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

24
app/admin/ep/saga/BUILD Normal file
View File

@@ -0,0 +1,24 @@
package(default_visibility = ["//visibility:public"])
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/admin/ep/saga/api/grpc/v1:all-srcs",
"//app/admin/ep/saga/cmd:all-srcs",
"//app/admin/ep/saga/conf:all-srcs",
"//app/admin/ep/saga/dao:all-srcs",
"//app/admin/ep/saga/http:all-srcs",
"//app/admin/ep/saga/model:all-srcs",
"//app/admin/ep/saga/server/grpc:all-srcs",
"//app/admin/ep/saga/service:all-srcs",
],
tags = ["automanaged"],
)

View File

@@ -0,0 +1,76 @@
# ep-saga
## v2.1.8
> 1.解决企业微信获取user ID为空的问题.
## v2.1.7
> 1.增加branch同步&统计branch聚合数据.
## v2.1.6
> 1.统计mr聚合数据
## v2.1.5
> 1.增加mr同步
## v2.1.4
> 1.adjust some sync time
## v2.1.3
> 1.adjust some data sync code structure
## v2.1.2
> 1.for wechat failed data
## v2.1.1
> 1.for log change
## v2.1.0
> 1.增加同步member、emoji、discussion数据
## v2.0.9
> 1.for pipeline sync
## v2.0.8
> 1.增加note同步数据
## v2.0.7
> 1.pipeline running time 修复 bug
## v2.0.6
> 1.增加企业微信发送记录
## v2.0.5
> 1.调整部分数据的字段以及特殊字符处理
## v2.0.4
> 1.修复企业微信redis存储过期时间
## v2.0.3
> 1.修复企业微信存db报错
## v2.0.2
> 1.恢复先前临时注释的几个gitlab api函数
## v2.0.1
> 1.pipeline异常实时告警
## v2.0.0
> 1.企业微信接口
> 2.数据收集、同步、计算
> 3.sven配置更改saga
> 4.pipeline异常实时告警等
## v1.0.4
> 1.增加MR等信息收集和查询接口
## v1.0.3
> 1.增加和优化commit等信息前端查询接口
## v1.0.2
> 1.更改CONTRIBUTORS.md owner
## v1.0.1
> 1.添加定时收集仓库信息的功能
## v1.0.0
> 1.初始化项目

View File

@@ -0,0 +1,9 @@
# Owner
zhanglin
# Author
all
# Reviewer
muyang
weiwu

13
app/admin/ep/saga/OWNERS Normal file
View File

@@ -0,0 +1,13 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- zhanglin
labels:
- admin
- admin/ep/saga
- ep
options:
no_parent_owners: true
reviewers:
- muyang
- weiwu

View File

@@ -0,0 +1,53 @@
load(
"@io_bazel_rules_go//proto:def.bzl",
"go_proto_library",
)
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
proto_library(
name = "v1_proto",
srcs = ["api.proto"],
tags = ["automanaged"],
)
go_proto_library(
name = "v1_go_proto",
compilers = ["@io_bazel_rules_go//proto:go_grpc"],
importpath = "go-common/app/admin/ep/saga/api/grpc/v1",
proto = ":v1_proto",
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = [],
embed = [":v1_go_proto"],
importpath = "go-common/app/admin/ep/saga/api/grpc/v1",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"@com_github_gogo_protobuf//proto:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_x_net//context: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,596 @@
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: app/admin/ep/saga/api/grpc/v1/api.proto
package v1
import proto "github.com/gogo/protobuf/proto"
import fmt "fmt"
import math "math"
import (
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
)
import io "io"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
type PushMsgReq struct {
Username []string `protobuf:"bytes,1,rep,name=username" json:"username,omitempty"`
Content string `protobuf:"bytes,2,opt,name=content,proto3" json:"content,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *PushMsgReq) Reset() { *m = PushMsgReq{} }
func (m *PushMsgReq) String() string { return proto.CompactTextString(m) }
func (*PushMsgReq) ProtoMessage() {}
func (*PushMsgReq) Descriptor() ([]byte, []int) {
return fileDescriptor_api_d78a3f3b47284b33, []int{0}
}
func (m *PushMsgReq) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *PushMsgReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_PushMsgReq.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalTo(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (dst *PushMsgReq) XXX_Merge(src proto.Message) {
xxx_messageInfo_PushMsgReq.Merge(dst, src)
}
func (m *PushMsgReq) XXX_Size() int {
return m.Size()
}
func (m *PushMsgReq) XXX_DiscardUnknown() {
xxx_messageInfo_PushMsgReq.DiscardUnknown(m)
}
var xxx_messageInfo_PushMsgReq proto.InternalMessageInfo
func (m *PushMsgReq) GetUsername() []string {
if m != nil {
return m.Username
}
return nil
}
func (m *PushMsgReq) GetContent() string {
if m != nil {
return m.Content
}
return ""
}
type PushMsgReply struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *PushMsgReply) Reset() { *m = PushMsgReply{} }
func (m *PushMsgReply) String() string { return proto.CompactTextString(m) }
func (*PushMsgReply) ProtoMessage() {}
func (*PushMsgReply) Descriptor() ([]byte, []int) {
return fileDescriptor_api_d78a3f3b47284b33, []int{1}
}
func (m *PushMsgReply) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *PushMsgReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_PushMsgReply.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalTo(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (dst *PushMsgReply) XXX_Merge(src proto.Message) {
xxx_messageInfo_PushMsgReply.Merge(dst, src)
}
func (m *PushMsgReply) XXX_Size() int {
return m.Size()
}
func (m *PushMsgReply) XXX_DiscardUnknown() {
xxx_messageInfo_PushMsgReply.DiscardUnknown(m)
}
var xxx_messageInfo_PushMsgReply proto.InternalMessageInfo
func init() {
proto.RegisterType((*PushMsgReq)(nil), "test.ep.sagaadmin.v1.PushMsgReq")
proto.RegisterType((*PushMsgReply)(nil), "test.ep.sagaadmin.v1.PushMsgReply")
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// SagaAdminClient is the client API for SagaAdmin service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type SagaAdminClient interface {
PushMsg(ctx context.Context, in *PushMsgReq, opts ...grpc.CallOption) (*PushMsgReply, error)
}
type sagaAdminClient struct {
cc *grpc.ClientConn
}
func NewSagaAdminClient(cc *grpc.ClientConn) SagaAdminClient {
return &sagaAdminClient{cc}
}
func (c *sagaAdminClient) PushMsg(ctx context.Context, in *PushMsgReq, opts ...grpc.CallOption) (*PushMsgReply, error) {
out := new(PushMsgReply)
err := c.cc.Invoke(ctx, "/test.ep.sagaadmin.v1.SagaAdmin/PushMsg", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// SagaAdminServer is the server API for SagaAdmin service.
type SagaAdminServer interface {
PushMsg(context.Context, *PushMsgReq) (*PushMsgReply, error)
}
func RegisterSagaAdminServer(s *grpc.Server, srv SagaAdminServer) {
s.RegisterService(&_SagaAdmin_serviceDesc, srv)
}
func _SagaAdmin_PushMsg_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(PushMsgReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SagaAdminServer).PushMsg(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/test.ep.sagaadmin.v1.SagaAdmin/PushMsg",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SagaAdminServer).PushMsg(ctx, req.(*PushMsgReq))
}
return interceptor(ctx, in, info, handler)
}
var _SagaAdmin_serviceDesc = grpc.ServiceDesc{
ServiceName: "test.ep.sagaadmin.v1.SagaAdmin",
HandlerType: (*SagaAdminServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "PushMsg",
Handler: _SagaAdmin_PushMsg_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "app/admin/ep/saga/api/grpc/v1/api.proto",
}
func (m *PushMsgReq) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalTo(dAtA)
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *PushMsgReq) MarshalTo(dAtA []byte) (int, error) {
var i int
_ = i
var l int
_ = l
if len(m.Username) > 0 {
for _, s := range m.Username {
dAtA[i] = 0xa
i++
l = len(s)
for l >= 1<<7 {
dAtA[i] = uint8(uint64(l)&0x7f | 0x80)
l >>= 7
i++
}
dAtA[i] = uint8(l)
i++
i += copy(dAtA[i:], s)
}
}
if len(m.Content) > 0 {
dAtA[i] = 0x12
i++
i = encodeVarintApi(dAtA, i, uint64(len(m.Content)))
i += copy(dAtA[i:], m.Content)
}
if m.XXX_unrecognized != nil {
i += copy(dAtA[i:], m.XXX_unrecognized)
}
return i, nil
}
func (m *PushMsgReply) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalTo(dAtA)
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *PushMsgReply) MarshalTo(dAtA []byte) (int, error) {
var i int
_ = i
var l int
_ = l
if m.XXX_unrecognized != nil {
i += copy(dAtA[i:], m.XXX_unrecognized)
}
return i, nil
}
func encodeVarintApi(dAtA []byte, offset int, v uint64) int {
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return offset + 1
}
func (m *PushMsgReq) Size() (n int) {
var l int
_ = l
if len(m.Username) > 0 {
for _, s := range m.Username {
l = len(s)
n += 1 + l + sovApi(uint64(l))
}
}
l = len(m.Content)
if l > 0 {
n += 1 + l + sovApi(uint64(l))
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
return n
}
func (m *PushMsgReply) Size() (n int) {
var l int
_ = l
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
return n
}
func sovApi(x uint64) (n int) {
for {
n++
x >>= 7
if x == 0 {
break
}
}
return n
}
func sozApi(x uint64) (n int) {
return sovApi(uint64((x << 1) ^ uint64((int64(x) >> 63))))
}
func (m *PushMsgReq) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowApi
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: PushMsgReq: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: PushMsgReq: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Username", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowApi
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthApi
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Username = append(m.Username, string(dAtA[iNdEx:postIndex]))
iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Content", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowApi
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthApi
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Content = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipApi(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthApi
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *PushMsgReply) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowApi
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: PushMsgReply: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: PushMsgReply: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
default:
iNdEx = preIndex
skippy, err := skipApi(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthApi
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipApi(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowApi
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
wireType := int(wire & 0x7)
switch wireType {
case 0:
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowApi
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
iNdEx++
if dAtA[iNdEx-1] < 0x80 {
break
}
}
return iNdEx, nil
case 1:
iNdEx += 8
return iNdEx, nil
case 2:
var length int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowApi
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
length |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
iNdEx += length
if length < 0 {
return 0, ErrInvalidLengthApi
}
return iNdEx, nil
case 3:
for {
var innerWire uint64
var start int = iNdEx
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowApi
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
innerWire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
innerWireType := int(innerWire & 0x7)
if innerWireType == 4 {
break
}
next, err := skipApi(dAtA[start:])
if err != nil {
return 0, err
}
iNdEx = start + next
}
return iNdEx, nil
case 4:
return iNdEx, nil
case 5:
iNdEx += 4
return iNdEx, nil
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
}
panic("unreachable")
}
var (
ErrInvalidLengthApi = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowApi = fmt.Errorf("proto: integer overflow")
)
func init() {
proto.RegisterFile("app/admin/ep/saga/api/grpc/v1/api.proto", fileDescriptor_api_d78a3f3b47284b33)
}
var fileDescriptor_api_d78a3f3b47284b33 = []byte{
// 200 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4f, 0x2c, 0x28, 0xd0,
0x4f, 0x4c, 0xc9, 0xcd, 0xcc, 0xd3, 0x4f, 0x2d, 0xd0, 0x2f, 0x4e, 0x4c, 0x4f, 0xd4, 0x4f, 0x2c,
0xc8, 0xd4, 0x4f, 0x2f, 0x2a, 0x48, 0xd6, 0x2f, 0x33, 0x04, 0xb1, 0xf5, 0x0a, 0x8a, 0xf2, 0x4b,
0xf2, 0x85, 0x44, 0x4a, 0x52, 0x8b, 0x4b, 0xf4, 0x52, 0x0b, 0xf4, 0x40, 0x6a, 0xc0, 0x1a, 0xf4,
0xca, 0x0c, 0x95, 0x9c, 0xb8, 0xb8, 0x02, 0x4a, 0x8b, 0x33, 0x7c, 0x8b, 0xd3, 0x83, 0x52, 0x0b,
0x85, 0xa4, 0xb8, 0x38, 0x4a, 0x8b, 0x53, 0x8b, 0xf2, 0x12, 0x73, 0x53, 0x25, 0x18, 0x15, 0x98,
0x35, 0x38, 0x83, 0xe0, 0x7c, 0x21, 0x09, 0x2e, 0xf6, 0xe4, 0xfc, 0xbc, 0x92, 0xd4, 0xbc, 0x12,
0x09, 0x26, 0x05, 0x46, 0x0d, 0xce, 0x20, 0x18, 0x57, 0x89, 0x8f, 0x8b, 0x07, 0x6e, 0x46, 0x41,
0x4e, 0xa5, 0x51, 0x0c, 0x17, 0x67, 0x70, 0x62, 0x7a, 0xa2, 0x23, 0xc8, 0x0e, 0x21, 0x7f, 0x2e,
0x76, 0xa8, 0xa4, 0x90, 0x82, 0x1e, 0x36, 0x27, 0xe8, 0x21, 0xec, 0x97, 0x52, 0x22, 0xa0, 0xa2,
0x20, 0xa7, 0xd2, 0x49, 0xe0, 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92,
0x63, 0x8c, 0x62, 0x2a, 0x33, 0x4c, 0x62, 0x03, 0x7b, 0xd0, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff,
0xac, 0x58, 0xa2, 0x9e, 0x0b, 0x01, 0x00, 0x00,
}

View File

@@ -0,0 +1,16 @@
syntax = "proto3";
package test.ep.sagaadmin.v1;
option go_package = "v1";
message PushMsgReq {
repeated string username = 1;
string content = 2;
}
message PushMsgReply {}
service SagaAdmin {
rpc PushMsg(PushMsgReq) returns(PushMsgReply);
}

View File

@@ -0,0 +1,43 @@
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 = ["saga-admin-test.toml"],
importpath = "go-common/app/admin/ep/saga/cmd",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/admin/ep/saga/conf:go_default_library",
"//app/admin/ep/saga/http:go_default_library",
"//app/admin/ep/saga/server/grpc:go_default_library",
"//app/admin/ep/saga/service:go_default_library",
"//library/log: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,56 @@
package main
import (
"context"
"flag"
"os"
"os/signal"
"syscall"
"time"
"go-common/app/admin/ep/saga/conf"
"go-common/app/admin/ep/saga/http"
"go-common/app/admin/ep/saga/server/grpc"
"go-common/app/admin/ep/saga/service"
"go-common/library/log"
)
const (
_durationForClosingServer = 2 // second
)
func main() {
flag.Parse()
if err := conf.Init(); err != nil {
log.Error("conf.Init() error(%v)", err)
panic(err)
}
log.Init(conf.Conf.Log)
defer log.Close()
log.Info("saga-admin start")
s := service.New()
http.Init(s)
grpcsvr, err := grpc.New(nil, s.Wechat())
if err != nil {
panic(err)
}
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
for {
si := <-c
log.Info("saga-admin get a signal %s", si.String())
switch si {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
grpcsvr.Shutdown(context.Background())
log.Info("saga-admin exit")
s.Close()
time.Sleep(_durationForClosingServer * time.Second)
return
case syscall.SIGHUP:
default:
return
}
}
}

View File

@@ -0,0 +1,145 @@
[bm]
addr = "0.0.0.0:8000"
maxListen = 10000
timeout = "1000s"
[httpClient]
key = "c05dd4e1638a8af0"
secret = "7daa7f8c06cd33c5c3067063c746fdcb"
dial = "2s"
timeout = "10s"
keepAlive = "60s"
timer = 1000
[httpClient.breaker]
window = "10s"
sleep = "2000ms"
bucket = 10
ratio = 0.5
request = 100
[orm]
dsn = "root:123456@tcp(172.16.60.47:3306)/saga?timeout=20000ms&readTimeout=20000ms&writeTimeout=20000ms&parseTime=true&loc=Local&charset=utf8,utf8mb4"
active = 5
idle = 5
idleTimeout = "4h"
[memcache]
mcRecordExpire = "720h"
[memcache.mc]
name = "saga-admin"
proto = "tcp"
addr = "172.18.33.130:11211"
idle = 5
active = 10
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "10s"
[redis]
active = 100
idle = 100
idleTimeout = "3s"
waitTimeout = "3s"
wait = false
name = "redis"
proto = "tcp"
addr = "172.22.33.137:6821"
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
[permit]
[permit.Memcache]
name = "go-business/auth"
proto = "tcp"
addr = "172.18.33.61:11232"
active = 10
idle = 10
dialTimeout = "1s"
readTimeout = "1s"
writeTimeout = "1s"
idleTimeout = "80s"
[property]
[property.gitlab]
api = "http://git-test.bilibili.co/api/v4"
token = ""
[property.git]
api = "http://git.bilibili.co/api/v4"
token = ""
checkCron = "* */15 * * * ?"
userlist = ["wuwei"]
[[property.git.alertpipeline]]
projectName = "andruid"
projectID = 5822
runningtimeout = 60
runningrate = 50
runningthreshold = 5
pendingtimeout = 2
pendingthreshold = 5
[[property.git.alertpipeline]]
projectName = "loktar"
projectID = 4928
runningtimeout = 20
runningrate = 50
runningthreshold = 3
pendingtimeout = 10
pendingthreshold = 5
[property.syncproject]
checkCron = "0 0 0 ? * 0"
[property.syncdata]
syncalltime = false
defaultsyncdays = 1
checkCron = "0 0 22 ? * 4"
wechatUser = ["wuwei","zhanglin"]
[property.department]
label = "主站 直播 bplus 开放平台 创作中心 商业产品 数据中心 视频云 游戏 火鸟"
value = "mainsite live bplus openplatform creative advertising datacenter videocloud game firebird"
[property.business]
label = "android iOS 后端 前端"
value = "android ios service web"
[property.defaultproject]
projectIDs=[682,4928,5822]
status = ["created","pending","running","failed","success","canceled","skipped","manual"]
types = [1]
commitmrtypes = [0,1]
[property.group]
name = "android ios live-dev"
department = "mainsite mainsite live"
business = "android ios service"
[property.mail]
host = "smtp.exmail.qq.com"
port = 465
address = "***"
pwd = "***"
name = "SAGA"
[property.wechat]
appId = 1000047
appSecret = ""
redisExpireWechat=3000
[property.contact]
appId = 9527
appSecret = ""
[property.sven]
configValue="http://fat1-sven.bilibili.co/x/admin/config/config/value"
configs="http://fat1-sven.bilibili.co/x/admin/config/config/configs"
configUpdate="http://fat1-sven.bilibili.co/x/admin/config/home/config/update"
tagUpdate="http://fat1-sven.bilibili.co/x/admin/config/home/tag/update"
[property.sven.sagaConfigsParam]
filename = "1254"
appName="test.ep.saga"
treeID=56733
env = "uat"
zone = "sh001"
buildID=68
build="v1.0"
token = "f6a596d04e69b4e39cf225a8f0748312"
increment = 1
force=1
userList=["wuwei","zhanglin","maojian","shenyue"]

View File

@@ -0,0 +1,42 @@
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/ep/saga/conf",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/admin/ep/saga/model:go_default_library",
"//library/cache/memcache:go_default_library",
"//library/cache/redis:go_default_library",
"//library/conf:go_default_library",
"//library/database/orm: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/trace:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/BurntSushi/toml: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,197 @@
package conf
import (
"flag"
"strings"
"go-common/app/admin/ep/saga/model"
"go-common/library/cache/memcache"
"go-common/library/cache/redis"
"go-common/library/conf"
"go-common/library/database/orm"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/permit"
"go-common/library/net/trace"
xtime "go-common/library/time"
"github.com/BurntSushi/toml"
"github.com/pkg/errors"
)
const (
_configKey = "saga-admin.toml"
)
var (
confPath string
client *conf.Client
// Conf store the global config
Conf = &Config{}
reload chan bool
)
// Config def.
type Config struct {
Tracer *trace.Config
BM *bm.ServerConfig
HTTPClient *bm.ClientConfig
Memcache *Memcache
Redis *redis.Config
Log *log.Config
ORM *orm.Config
Permit *permit.Config2
Property *Property
}
// Memcache config.
type Memcache struct {
MC *memcache.Config
MCRecordExpire xtime.Duration
}
// Property config for biz logic.
type Property struct {
Gitlab *struct {
API string // gitlab api host
Token string // saga 账户 access token
}
Git *struct {
API string // gitlab api host
Token string // saga 账户 access token
CheckCron string
UserList []string
AlertPipeline []*model.AlertPipeline
}
SyncProject *struct {
CheckCron string
}
SyncData *struct {
SyncAllTime bool
DefaultSyncDays int
CheckCron string
CheckCronAll string
CheckCronWeek string
WechatUser []string
}
Department *model.PairKey
Business *model.PairKey
DeInfo []*model.PairKey
BuInfo []*model.PairKey
Mail *struct {
Host string
Port int
Address string
Pwd string
Name string
}
Wechat *model.AppConfig
Contact *model.AppConfig
Group *struct {
Name string
Department string
Business string
}
DefaultProject *struct {
ProjectIDs []int
Status []string
Types []int
}
Sven *struct {
ConfigValue string
Configs string
ConfigUpdate string
TagUpdate string
ConfigsParam *model.ConfigsParam
SagaConfigsParam *model.SagaConfigsParam
}
}
func init() {
flag.StringVar(&confPath, "conf", "", "config path")
reload = make(chan bool, 10)
}
// Init init conf.
func Init() (err error) {
if confPath == "" {
return configCenter()
}
if _, err = toml.DecodeFile(confPath, &Conf); err != nil {
log.Error("toml.DecodeFile(%s) err(%+v)", confPath, err)
return
}
Conf = parseTeamInfo(Conf)
return
}
func configCenter() (err error) {
if client, err = conf.New(); err != nil {
panic(err)
}
if err = load(); err != nil {
return
}
client.WatchAll()
go func() {
for range client.Event() {
log.Info("config reload")
if load() != nil {
log.Error("config reload error (%v)", err)
} else {
reload <- true
}
}
}()
return
}
func load() (err error) {
var (
s string
ok bool
tmpConf *Config
)
if s, ok = client.Value(_configKey); !ok {
err = errors.Errorf("load config center error [%s]", _configKey)
return
}
if _, err = toml.Decode(s, &tmpConf); err != nil {
err = errors.Wrapf(err, "could not decode config err(%+v)", err)
return
}
Conf = parseTeamInfo(tmpConf)
return
}
func parseTeamInfo(c *Config) *Config {
DeLabel := strings.Fields(c.Property.Department.Label)
DeValue := strings.Fields(c.Property.Department.Value)
for i := 0; i < len(DeLabel); i++ {
info := &model.PairKey{
Label: DeLabel[i],
Value: DeValue[i],
}
c.Property.DeInfo = append(c.Property.DeInfo, info)
}
buLabel := strings.Fields(c.Property.Business.Label)
buValue := strings.Fields(c.Property.Business.Value)
for i := 0; i < len(buLabel); i++ {
info := &model.PairKey{
Label: buLabel[i],
Value: buValue[i],
}
c.Property.BuInfo = append(c.Property.BuInfo, info)
}
/*for _, r := range c.Property.Developer {
r.Total = r.Android + r.Ios + r.Service + r.Web
}*/
return c
}

View File

@@ -0,0 +1,70 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"dao.go",
"db_project.go",
"db_sync.go",
"db_task.go",
"db_wechat.go",
"http.go",
"mc.go",
"redis.go",
],
importpath = "go-common/app/admin/ep/saga/dao",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/admin/ep/saga/conf:go_default_library",
"//app/admin/ep/saga/model:go_default_library",
"//library/cache/memcache:go_default_library",
"//library/cache/redis:go_default_library",
"//library/database/orm:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//vendor/github.com/jinzhu/gorm: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"],
)
go_test(
name = "go_default_test",
srcs = [
"dao_test.go",
"db_project_test.go",
"db_sync_test.go",
"db_wechat_test.go",
"mc_test.go",
"redis_test.go",
],
embed = [":go_default_library"],
tags = ["automanaged"],
deps = [
"//app/admin/ep/saga/conf:go_default_library",
"//app/admin/ep/saga/model:go_default_library",
"//app/admin/ep/saga/service/utils:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)

View File

@@ -0,0 +1,57 @@
package dao
import (
"context"
"time"
"go-common/app/admin/ep/saga/conf"
"go-common/library/cache/memcache"
"go-common/library/cache/redis"
"go-common/library/database/orm"
bm "go-common/library/net/http/blademaster"
"github.com/jinzhu/gorm"
)
// Dao def
type Dao struct {
// cache
httpClient *bm.Client
db *gorm.DB
mc *memcache.Pool
redis *redis.Pool
mcRecordExpire int32
}
// New create instance of Dao
func New() (d *Dao) {
d = &Dao{
mc: memcache.NewPool(conf.Conf.Memcache.MC),
httpClient: bm.NewClient(conf.Conf.HTTPClient),
db: orm.NewMySQL(conf.Conf.ORM),
redis: redis.NewPool(conf.Conf.Redis),
mcRecordExpire: int32(time.Duration(conf.Conf.Memcache.MCRecordExpire) / time.Second),
}
return
}
// Ping dao.
func (d *Dao) Ping(c context.Context) (err error) {
if err = d.pingRedis(c); err != nil {
return
}
if err = d.pingMC(c); err != nil {
return
}
return d.db.DB().Ping()
}
// Close dao.
func (d *Dao) Close() {
if d.mc != nil {
d.mc.Close()
}
if d.db != nil {
d.db.Close()
}
}

View File

@@ -0,0 +1,24 @@
package dao
import (
"flag"
"os"
"testing"
"go-common/app/admin/ep/saga/conf"
)
var (
d *Dao
)
func TestMain(m *testing.M) {
flag.Set("conf", "../cmd/saga-admin-test.toml")
flag.Parse()
if err := conf.Init(); err != nil {
panic(err)
}
d = New()
defer d.Close()
os.Exit(m.Run())
}

View File

@@ -0,0 +1,138 @@
package dao
import (
"go-common/app/admin/ep/saga/conf"
"go-common/app/admin/ep/saga/model"
"github.com/jinzhu/gorm"
pkgerr "github.com/pkg/errors"
)
// ProjectExist check the project exist or not
func (d *Dao) ProjectExist(projID int) (exist bool, err error) {
var count int
if err = pkgerr.WithStack(d.db.Model(&model.ProjectInfo{}).Where(&model.ProjectInfo{ProjectID: projID}).Count(&count).Error); err != nil {
return
}
if count > 0 {
exist = true
}
return
}
// FavoriteProjects get user's favorite projects
func (d *Dao) FavoriteProjects(userName string) (favorites []*model.ProjectFavorite, err error) {
//err = pkgerr.WithStack(d.db.Where(&model.ProjectFavorite{UserName: userName}).Find(&favorites).Error)
err = pkgerr.WithStack(d.db.Where("user_name = ?", userName).Find(&favorites).Error)
return
}
// AddFavorite add favorite project for user
func (d *Dao) AddFavorite(userName string, projID int) (err error) {
return pkgerr.WithStack(d.db.Create(&model.ProjectFavorite{UserName: userName, ProjID: projID}).Error)
}
// DelFavorite delete favorite project for user
func (d *Dao) DelFavorite(userName string, projID int) (err error) {
return pkgerr.WithStack(d.db.Delete(model.ProjectFavorite{UserName: userName, ProjID: projID}).Error)
}
// AddProjectInfo add ProjectInfo
func (d *Dao) AddProjectInfo(projectInfo *model.ProjectInfo) (err error) {
return pkgerr.WithStack(d.db.Create(projectInfo).Error)
}
// ProjectsInfo all the projects in saga
func (d *Dao) ProjectsInfo() (projects []*model.ProjectInfo, err error) {
err = pkgerr.WithStack(d.db.Find(&projects).Error)
return
}
// ProjectInfoByID query ProjectInfo by ID
func (d *Dao) ProjectInfoByID(projectID int) (projectInfo *model.ProjectInfo, err error) {
projectInfo = &model.ProjectInfo{}
err = pkgerr.WithStack(d.db.Where("project_id = ?", projectID).First(projectInfo).Error)
return
}
// UpdateProjectInfo update
func (d *Dao) UpdateProjectInfo(projectID int, projectInfo *model.ProjectInfo) (err error) {
return pkgerr.WithStack(d.db.Model(&model.ProjectInfo{}).Where("project_id = ?", projectID).Update(projectInfo).Error)
}
// HasProjectInfo is exist project info in database.
func (d *Dao) HasProjectInfo(projectID int) (b bool, err error) {
var size int64
if err = pkgerr.WithStack(d.db.Model(&model.ProjectInfo{}).Where("project_id = ?", projectID).Count(&size).Error); err != nil {
return
}
b = size > 0
return
}
// QueryProjectInfo query Project Info.
func (d *Dao) QueryProjectInfo(ifPage bool, req *model.ProjectInfoRequest) (total int, projectInfo []*model.ProjectInfo, err error) {
var (
projectIDs = conf.Conf.Property.DefaultProject.ProjectIDs
db *gorm.DB
)
gDB := d.db.Model(&model.ProjectInfo{})
if req.Name != "" {
gDB = gDB.Where("name = ?", req.Name)
}
if req.Department != "" {
gDB = gDB.Where("department = ?", req.Department)
}
if req.Business != "" {
gDB = gDB.Where("business = ?", req.Business)
}
if err = pkgerr.WithStack(gDB.Count(&total).Error); err != nil {
return
}
if ifPage {
if req.PageNum == 1 {
db = gDB.Order("name DESC").Where("project_id in (?)", projectIDs).Find(&projectInfo)
} else {
db = gDB.Order("name DESC").Offset((req.PageNum - 2) * req.PageSize).Limit(req.PageSize).Find(&projectInfo)
}
} else {
db = gDB.Find(&projectInfo)
}
if db.Error != nil {
if db.RecordNotFound() {
err = nil
} else {
err = pkgerr.WithStack(db.Error)
}
}
return
}
// QueryConfigInfo query saga and runner Info.
func (d *Dao) QueryConfigInfo(name, department, business, queryObject string) (total int, err error) {
gDB := d.db.Model(&model.ProjectInfo{})
if name != "" {
gDB = gDB.Where("name = ?", name)
}
if department != "" {
gDB = gDB.Where("department = ?", department)
}
if business != "" {
gDB = gDB.Where("business = ?", business)
}
if queryObject == model.ObjectSaga {
gDB = gDB.Where("Saga = ?", true)
} else if queryObject == model.ObjectRunner {
gDB = gDB.Where("Runner = ?", true)
} else {
return
}
err = pkgerr.WithStack(gDB.Count(&total).Error)
return
}

View File

@@ -0,0 +1,203 @@
package dao
import (
"testing"
"go-common/app/admin/ep/saga/model"
"github.com/smartystreets/goconvey/convey"
)
func TestProjectExist(t *testing.T) {
convey.Convey("ProjectExist", t, func(ctx convey.C) {
var (
projectID = int(682)
projectID2 = int(6820)
)
ctx.Convey("When projectID exist", func(ctx convey.C) {
b, err := d.ProjectExist(projectID)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(b, convey.ShouldEqual, true)
})
})
ctx.Convey("When projectID not exist", func(ctx convey.C) {
bb, err := d.ProjectExist(projectID2)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(bb, convey.ShouldEqual, false)
})
})
})
}
func TestFavorite(t *testing.T) {
convey.Convey("Favorite project", t, func(ctx convey.C) {
var (
userName = "zhangsan"
projectID = 333
)
ctx.Convey("add favorite project", func(ctx convey.C) {
err := d.AddFavorite(userName, projectID)
ctx.Convey("The err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
ctx.Convey("get favorite project", func(ctx convey.C) {
favorites, err := d.FavoriteProjects(userName)
ctx.Convey("The err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(len(favorites), convey.ShouldEqual, 1)
ctx.So(favorites[0].UserName, convey.ShouldEqual, "zhangsan")
ctx.So(favorites[0].ProjID, convey.ShouldEqual, 333)
})
})
ctx.Convey("delete favorite project", func(ctx convey.C) {
err := d.DelFavorite(userName, projectID)
ctx.Convey("delete err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
favorites, err := d.FavoriteProjects(userName)
ctx.Convey("get err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(len(favorites), convey.ShouldEqual, 0)
})
})
})
}
func TestProjectInfoByID(t *testing.T) {
convey.Convey("ProjectInfoByID", t, func(ctx convey.C) {
ctx.Convey("add project info", func(ctx convey.C) {
addInfo := &model.ProjectInfo{
ProjectID: 11111,
Name: "myProject",
Description: "myProject des",
WebURL: "git@git.bilibili.test/test.git",
Repo: "git@git.bilibili.test/test.git",
DefaultBranch: "master",
Saga: true,
Runner: false,
}
err := d.AddProjectInfo(addInfo)
ctx.Convey("Add Project Info. Then err should be nil. ", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
info, err := d.ProjectInfoByID(11111)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(info.Name, convey.ShouldEqual, "myProject")
ctx.So(info.Repo, convey.ShouldEqual, "git@git.bilibili.test/test.git")
ctx.So(info.Saga, convey.ShouldEqual, true)
})
})
ctx.Convey("update project info", func(ctx convey.C) {
updateInfo := &model.ProjectInfo{
ProjectID: 11111,
Name: "lisi",
Description: "lisi des",
WebURL: "git@git.bilibili.test/test.git",
Repo: "git@git.bilibili.test/test.git",
DefaultBranch: "dev",
Saga: true,
Runner: false,
}
err := d.UpdateProjectInfo(11111, updateInfo)
ctx.Convey("update Project Info. Then err should be nil. ", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
info, err := d.ProjectInfoByID(11111)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(info.Name, convey.ShouldEqual, "lisi")
ctx.So(info.Description, convey.ShouldEqual, "lisi des")
ctx.So(info.DefaultBranch, convey.ShouldEqual, "dev")
})
})
ctx.Convey("query not exist project info", func(ctx convey.C) {
_, err := d.ProjectInfoByID(6820)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldNotBeNil)
})
})
})
}
func TestDaoHasProjectInfo(t *testing.T) {
convey.Convey("HasProjectInfo", t, func(ctx convey.C) {
var (
projectID = int(682)
projectID2 = int(6820)
)
ctx.Convey("When projectID exist", func(ctx convey.C) {
b, err := d.HasProjectInfo(projectID)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(b, convey.ShouldEqual, true)
})
})
ctx.Convey("When projectID not exist", func(ctx convey.C) {
b, err := d.HasProjectInfo(projectID2)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(b, convey.ShouldEqual, false)
})
})
})
}
func TestQueryProjectInfo(t *testing.T) {
convey.Convey("QueryProjectInfo", t, func(ctx convey.C) {
var (
projectName = "andruid"
projectUrl = "git@git-test.bilibili.co:android/andruid.git"
projectInfoRequest = &model.ProjectInfoRequest{
Name: projectName,
}
)
ctx.Convey("query project exist", func(ctx convey.C) {
var url string
total, projectInfo, err := d.QueryProjectInfo(false, projectInfoRequest)
for _, project := range projectInfo {
url = project.Repo
}
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(total, convey.ShouldEqual, 1)
ctx.So(url, convey.ShouldEqual, projectUrl)
})
})
})
}
func TestQueryConfigInfo(t *testing.T) {
convey.Convey("QueryConfigInfo", t, func(ctx convey.C) {
var (
projectName = "Kratos"
projectName2 = "android-blue"
queryObject = "saga"
)
ctx.Convey("query saga config exist", func(ctx convey.C) {
total, err := d.QueryConfigInfo(projectName, "", "", queryObject)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(total, convey.ShouldEqual, 1)
})
})
ctx.Convey("query saga config not exist", func(ctx convey.C) {
total, err := d.QueryConfigInfo(projectName2, "", "", queryObject)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(total, convey.ShouldEqual, 0)
})
})
})
}

View File

@@ -0,0 +1,398 @@
package dao
import (
"context"
"time"
"go-common/app/admin/ep/saga/model"
pkgerr "github.com/pkg/errors"
)
/*-------------------------------------- commit ----------------------------------------*/
// HasCommit ...
func (d *Dao) HasCommit(projID int, commitID string) (total int, err error) {
err = pkgerr.WithStack(d.db.Model(&model.StatisticsCommits{}).Where("project_id = ? AND commit_id = ?", projID, commitID).Count(&total).Error)
return
}
// DelCommit ...
func (d *Dao) DelCommit(projID int, commitID string) (err error) {
return pkgerr.WithStack(d.db.Where("project_id = ? AND commit_id = ?", projID, commitID).Delete(&model.StatisticsCommits{}).Error)
}
// UpdateCommit ...
func (d *Dao) UpdateCommit(projID int, commitID string, commit *model.StatisticsCommits) (err error) {
return pkgerr.WithStack(d.db.Model(&model.StatisticsCommits{}).Where("project_id = ? AND commit_id = ?", projID, commitID).Update(commit).Error)
}
// CreateCommit ...
func (d *Dao) CreateCommit(req *model.StatisticsCommits) (err error) {
return pkgerr.WithStack(d.db.Create(req).Error)
}
// QueryCommitByID query commit info by ID
func (d *Dao) QueryCommitByID(projID int, commitID string) (commit *model.StatisticsCommits, err error) {
commit = &model.StatisticsCommits{}
err = pkgerr.WithStack(d.db.Where("project_id = ? AND commit_id = ?", projID, commitID).First(commit).Error)
return
}
// CountCommitByTime ...
func (d *Dao) CountCommitByTime(projID int, since, until string) (total int, err error) {
err = pkgerr.WithStack(d.db.Model(&model.StatisticsCommits{}).Where("project_id = ? AND created_at > ? AND created_at < ?", projID, since, until).Count(&total).Error)
return
}
// QueryProjectCommits ...
func (d *Dao) QueryProjectCommits(c context.Context, projectID int) (commits []*model.StatisticsCommits, err error) {
err = pkgerr.WithStack(d.db.Model(&model.StatisticsCommits{}).Where("project_id = ?", projectID).Find(&commits).Error)
return
}
/*-------------------------------------- issue ----------------------------------------*/
// HasIssue ...
func (d *Dao) HasIssue(projID, issueID int) (total int, err error) {
err = pkgerr.WithStack(d.db.Model(&model.StatisticsIssues{}).Where("project_id = ? AND issue_id = ?", projID, issueID).Count(&total).Error)
return
}
// UpdateIssue ...
func (d *Dao) UpdateIssue(projID, issueID int, issue *model.StatisticsIssues) (err error) {
return pkgerr.WithStack(d.db.Model(&model.StatisticsIssues{}).Where("project_id = ? AND issue_id = ?", projID, issueID).Update(issue).Error)
}
// CreateIssue ...
func (d *Dao) CreateIssue(req *model.StatisticsIssues) (err error) {
return pkgerr.WithStack(d.db.Create(req).Error)
}
/*-------------------------------------- runner ----------------------------------------*/
// HasRunner ...
func (d *Dao) HasRunner(projID, runnerID int) (total int, err error) {
err = pkgerr.WithStack(d.db.Model(&model.StatisticsRunners{}).Where("project_id = ? AND runner_id = ?", projID, runnerID).Count(&total).Error)
return
}
// UpdateRunner ...
func (d *Dao) UpdateRunner(projID, runnerID int, runner *model.StatisticsRunners) (err error) {
return pkgerr.WithStack(d.db.Model(&model.StatisticsRunners{}).Where("project_id = ? AND runner_id = ?", projID, runnerID).Update(runner).Error)
}
// CreateRunner ...
func (d *Dao) CreateRunner(req *model.StatisticsRunners) (err error) {
return pkgerr.WithStack(d.db.Create(req).Error)
}
/*-------------------------------------- job ----------------------------------------*/
// HasJob ...
func (d *Dao) HasJob(projID, jobID int) (total int, err error) {
err = pkgerr.WithStack(d.db.Model(&model.StatisticsJobs{}).Where("project_id = ? AND job_id=?", projID, jobID).Count(&total).Error)
return
}
// UpdateJob ...
func (d *Dao) UpdateJob(projID, jobID int, runner *model.StatisticsJobs) (err error) {
return pkgerr.WithStack(d.db.Model(&model.StatisticsJobs{}).Where("project_id = ? AND job_id = ?", projID, jobID).Update(runner).Error)
}
// CreateJob ...
func (d *Dao) CreateJob(req *model.StatisticsJobs) (err error) {
return pkgerr.WithStack(d.db.Create(req).Error)
}
// QueryJobsByTime ...
func (d *Dao) QueryJobsByTime(projID int, req *model.ProjectJobRequest, since, until string) (total int, jobs []*model.StatisticsJobs, err error) {
gDB := d.db.Model(&model.StatisticsJobs{}).Where("project_id = ? AND created_at > ? AND created_at < ?", projID, since, until)
if req.Branch != "" {
gDB = gDB.Where("ref = ?", req.Branch)
}
if req.User != "" {
gDB = gDB.Where("user_name = ?", req.User)
}
if req.Machine != "" {
gDB = gDB.Where("runner_description = ?", req.Machine)
}
if err = pkgerr.WithStack(gDB.Count(&total).Error); err != nil {
return
}
if err = pkgerr.WithStack(gDB.Find(&jobs).Error); err != nil {
return
}
return
}
/*-------------------------------------- note ----------------------------------------*/
// HasNote ...
func (d *Dao) HasNote(c context.Context, projectID, noteID int) (total int, err error) {
err = pkgerr.WithStack(d.db.Model(&model.StatisticsNotes{}).Where("project_id = ? AND note_id = ?", projectID, noteID).Count(&total).Error)
return
}
// UpdateNote ...
func (d *Dao) UpdateNote(c context.Context, projectID, noteID int, note *model.StatisticsNotes) (err error) {
return pkgerr.WithStack(d.db.Model(&model.StatisticsNotes{}).Where("project_id = ? AND note_id = ?", projectID, noteID).Update(note).Error)
}
// CreateNote ...
func (d *Dao) CreateNote(c context.Context, note *model.StatisticsNotes) (err error) {
return pkgerr.WithStack(d.db.Create(note).Error)
}
// NoteByMRIID ...
func (d *Dao) NoteByMRIID(c context.Context, projectID, mrIID int) (notes []*model.StatisticsNotes, err error) {
err = pkgerr.WithStack(d.db.Where("project_id = ? AND mr_iid = ? ", projectID, mrIID).Order("note_id desc").Find(&notes).Error)
return
}
// NoteByID ...
func (d *Dao) NoteByID(c context.Context, projectID, mrIID, noteID int) (note *model.StatisticsNotes, err error) {
note = &model.StatisticsNotes{}
err = pkgerr.WithStack(d.db.Where("project_id = ? AND mr_iid = ? AND note_id = ?", projectID, mrIID, noteID).First(note).Error)
return
}
/*-------------------------------------- mr ----------------------------------------*/
// CountMRByTime ...
func (d *Dao) CountMRByTime(projID int, since, until string) (total int, err error) {
err = pkgerr.WithStack(d.db.Model(&model.StatisticsMrs{}).Where("project_id = ? AND created_at > ? AND created_at < ?", projID, since, until).Count(&total).Error)
return
}
// MRByProjectID query mr by projectID
func (d *Dao) MRByProjectID(c context.Context, projectID int, since *time.Time, until *time.Time) (mrs []*model.StatisticsMrs, err error) {
gDB := d.db.Model(&model.StatisticsMrs{}).Where("project_id = ? ", projectID)
if since != nil || until != nil {
gDB = gDB.Where("updated_at >= ? AND updated_at <= ?", since, until)
}
err = pkgerr.WithStack(gDB.Find(&mrs).Error)
return
}
// HasMR ...
func (d *Dao) HasMR(c context.Context, projectID, mrIID int) (total int, err error) {
err = pkgerr.WithStack(d.db.Model(&model.StatisticsMrs{}).Where("project_id = ? AND mr_iid = ?", projectID, mrIID).Count(&total).Error)
return
}
// UpdateMR update
func (d *Dao) UpdateMR(c context.Context, projectID, mrIID int, mr *model.StatisticsMrs) (err error) {
return pkgerr.WithStack(d.db.Model(&model.StatisticsMrs{}).Where("project_id = ? AND mr_iid = ?", projectID, mrIID).Update(mr).Error)
}
// CreateMR ...
func (d *Dao) CreateMR(c context.Context, req *model.StatisticsMrs) (err error) {
return pkgerr.WithStack(d.db.Create(req).Error)
}
/*-------------------------------------- aggMR ----------------------------------------*/
// HasAggregateReviewer ...
func (d *Dao) HasAggregateReviewer(c context.Context, projectID, mrIID, reviewerID, reviewID int) (total int, err error) {
err = pkgerr.WithStack(d.db.Model(&model.AggregateMrReviewer{}).Where("project_id = ? and mr_iid = ? and reviewer_id=? and review_id = ? ", projectID, mrIID, reviewerID, reviewID).Count(&total).Error)
return
}
// UpdateAggregateReviewer ...
func (d *Dao) UpdateAggregateReviewer(c context.Context, projectID, mrIID, reviewerID, reviewID int, aggregateReviewer *model.AggregateMrReviewer) (err error) {
return pkgerr.WithStack(d.db.Model(&model.AggregateMrReviewer{}).Where("project_id = ? and mr_iid = ? and reviewer_id=? and review_id = ? ", projectID, mrIID, reviewerID, reviewID).Update(aggregateReviewer).Error)
}
// CreateAggregateReviewer ...
func (d *Dao) CreateAggregateReviewer(c context.Context, req *model.AggregateMrReviewer) (err error) {
return pkgerr.WithStack(d.db.Create(req).Error)
}
/*-------------------------------------- pipeline ----------------------------------------*/
// HasPipeline ...
func (d *Dao) HasPipeline(projID int, pipelineID int) (total int, err error) {
err = pkgerr.WithStack(d.db.Model(&model.StatisticsPipeline{}).Where("project_id = ? AND pipeline_id = ?", projID, pipelineID).Count(&total).Error)
return
}
// UpdatePipeline ...
func (d *Dao) UpdatePipeline(projID int, pipelineID int, pipeline *model.StatisticsPipeline) (err error) {
return pkgerr.WithStack(d.db.Model(&model.StatisticsPipeline{}).Where("project_id = ? AND pipeline_id = ?", projID, pipelineID).Update(pipeline).Error)
}
// CreatePipeline ...
func (d *Dao) CreatePipeline(req *model.StatisticsPipeline) (err error) {
return pkgerr.WithStack(d.db.Create(req).Error)
}
// QueryPipelinesByTime ...
func (d *Dao) QueryPipelinesByTime(projID int, req *model.PipelineDataReq, since, until string) (total, statNum int, pipelines []*model.StatisticsPipeline, err error) {
gDB := d.db.Model(&model.StatisticsPipeline{}).Where("project_id = ? AND created_at > ? AND created_at < ?", projID, since, until)
if req.Branch != "" {
gDB = gDB.Where("ref = ?", req.Branch)
}
if req.User != "" {
gDB = gDB.Where("user = ?", req.User)
}
if err = pkgerr.WithStack(gDB.Count(&total).Error); err != nil {
return
}
if req.State != "" {
gDB = gDB.Where("status = ?", req.State)
}
if err = pkgerr.WithStack(gDB.Count(&statNum).Error); err != nil {
return
}
if err = pkgerr.WithStack(gDB.Find(&pipelines).Error); err != nil {
return
}
return
}
/*-------------------------------------- member ----------------------------------------*/
// HasMember ...
func (d *Dao) HasMember(c context.Context, projectID, memberID int) (total int, err error) {
err = pkgerr.WithStack(d.db.Model(&model.StatisticsMembers{}).Where("project_id = ? AND member_id = ?", projectID, memberID).Count(&total).Error)
return
}
// UpdateMember ...
func (d *Dao) UpdateMember(c context.Context, projectID, memberID int, member *model.StatisticsMembers) (err error) {
err = pkgerr.WithStack(d.db.Model(&model.StatisticsMembers{}).Where("project_id = ? AND member_id = ?", projectID, memberID).Update(member).Error)
return
}
// QueryMemberByID ...
func (d *Dao) QueryMemberByID(c context.Context, projectID, memberID int) (member *model.StatisticsMembers, err error) {
err = pkgerr.WithStack(d.db.Model(&model.StatisticsMembers{}).Where("project_id = ? AND member_id = ?", projectID, memberID).First(&member).Error)
return
}
// CreateMember ...
func (d *Dao) CreateMember(c context.Context, member *model.StatisticsMembers) (err error) {
return pkgerr.WithStack(d.db.Create(member).Error)
}
/*-------------------------------------- emoji ----------------------------------------*/
// HasMRAwardEmoji ...
func (d *Dao) HasMRAwardEmoji(c context.Context, projectID, mrIID, awardEmojiID int) (total int, err error) {
err = pkgerr.WithStack(d.db.Model(&model.StatisticsMRAwardEmojis{}).Where("project_id = ? and mr_iid = ? and award_emoji_id=? ", projectID, mrIID, awardEmojiID).Count(&total).Error)
return
}
// UpdateMRAwardEmoji update
func (d *Dao) UpdateMRAwardEmoji(c context.Context, projectID, mrIID, awardEmojiID int, awardEmoji *model.StatisticsMRAwardEmojis) (err error) {
return pkgerr.WithStack(d.db.Model(&model.StatisticsMRAwardEmojis{}).Where("project_id = ? and mr_iid = ? and award_emoji_id=? ", projectID, mrIID, awardEmojiID).Update(awardEmoji).Error)
}
// CreateMRAwardEmoji ...
func (d *Dao) CreateMRAwardEmoji(c context.Context, awardEmoji *model.StatisticsMRAwardEmojis) (err error) {
return pkgerr.WithStack(d.db.Create(awardEmoji).Error)
}
// AwardEmojiByMRIID ...
func (d *Dao) AwardEmojiByMRIID(c context.Context, projectID, mrIID int) (AwardEmojis []*model.StatisticsMRAwardEmojis, err error) {
err = pkgerr.WithStack(d.db.Where("project_id = ? AND mr_iid = ? ", projectID, mrIID).Find(&AwardEmojis).Error)
return
}
/*-------------------------------------- discussion ----------------------------------------*/
// HasDiscussion ...
func (d *Dao) HasDiscussion(c context.Context, projectID, mrIID int, discussionID string) (total int, err error) {
err = pkgerr.WithStack(d.db.Model(&model.StatisticsDiscussions{}).Where("project_id = ? and mr_iid = ? and discussion_id=? ", projectID, mrIID, discussionID).Count(&total).Error)
return
}
// UpdateDiscussion ...
func (d *Dao) UpdateDiscussion(c context.Context, projectID, mrIID int, discussionID string, discussion *model.StatisticsDiscussions) (err error) {
err = pkgerr.WithStack(d.db.Model(&model.StatisticsDiscussions{}).Where("project_id = ? and mr_iid = ? and discussion_id=? ", projectID, mrIID, discussionID).Update(discussion).Error)
return
}
// CreateDiscussion ...
func (d *Dao) CreateDiscussion(c context.Context, discussion *model.StatisticsDiscussions) (err error) {
return pkgerr.WithStack(d.db.Create(discussion).Error)
}
// DiscussionsByMRIID ...
func (d *Dao) DiscussionsByMRIID(c context.Context, projectID, mrIID int) (discussions []*model.StatisticsDiscussions, err error) {
err = pkgerr.WithStack(d.db.Where("project_id = ? AND mr_iid = ? ", projectID, mrIID).Find(&discussions).Error)
return
}
/*-------------------------------------- Branch ----------------------------------------*/
// DeleteProjectBranch ...
func (d *Dao) DeleteProjectBranch(c context.Context, projectID int) error {
return pkgerr.WithStack(d.db.Model(&model.StatisticsBranches{}).Where("project_id = ?", projectID).Update("is_deleted", model.BranchDeleted).Error)
}
// HasBranch is exist project branch info in database.
func (d *Dao) HasBranch(c context.Context, projectID int, branchName string) (total int, err error) {
err = pkgerr.WithStack(d.db.Model(&model.StatisticsBranches{}).Where("project_id = ? AND branch_name = ? ", projectID, branchName).Count(&total).Error)
return
}
// UpdateBranch update
func (d *Dao) UpdateBranch(c context.Context, projectID int, branchName string, branch *model.StatisticsBranches) error {
return pkgerr.WithStack(d.db.Model(&model.StatisticsBranches{}).Where("project_id = ? AND branch_name = ? ", projectID, branchName).Save(branch).Error)
}
// CreateBranch query all the records in contact_infos
func (d *Dao) CreateBranch(c context.Context, branch *model.StatisticsBranches) error {
return pkgerr.WithStack(d.db.Create(branch).Error)
}
// QueryProjectBranch ...
func (d *Dao) QueryProjectBranch(c context.Context, projectID int) (b []*model.StatisticsBranches, err error) {
err = pkgerr.WithStack(d.db.Where("project_id = ? and is_deleted != ?", projectID, model.BranchDeleted).Find(&b).Error)
return
}
// QueryFirstBranch ...
func (d *Dao) QueryFirstBranch(c context.Context, projectID int, branchName string) (branch *model.StatisticsBranches, err error) {
branch = &model.StatisticsBranches{}
err = pkgerr.WithStack(d.db.Model(&model.StatisticsBranches{}).Where("project_id = ? AND branch_name = ? ", projectID, branchName).First(branch).Error)
return
}
// DeleteAggregateBranch ...
func (d *Dao) DeleteAggregateBranch(c context.Context, projectID int) error {
return pkgerr.WithStack(d.db.Model(&model.AggregateBranches{}).Where("project_id = ?", projectID).Update("is_deleted", model.BranchDeleted).Error)
}
// HasAggregateBranch ...
func (d *Dao) HasAggregateBranch(c context.Context, projectID int, branchName string) (total int, err error) {
err = pkgerr.WithStack(d.db.Model(&model.AggregateBranches{}).Where("project_id = ? AND branch_name = ? ", projectID, branchName).Count(&total).Error)
return
}
// UpdateAggregateBranch ...
func (d *Dao) UpdateAggregateBranch(c context.Context, projectID int, branchName string, aggregateBranch *model.AggregateBranches) error {
return pkgerr.WithStack(d.db.Model(&model.AggregateBranches{}).Where("project_id = ? AND branch_name = ?", projectID, branchName).Save(aggregateBranch).Error)
}
// CreateAggregateBranch ...
func (d *Dao) CreateAggregateBranch(c context.Context, aggregateBranch *model.AggregateBranches) error {
return pkgerr.WithStack(d.db.Create(aggregateBranch).Error)
}
// QueryFirstAggregateBranch ...
func (d *Dao) QueryFirstAggregateBranch(c context.Context, projectID int, branchName string) (branch *model.AggregateBranches, err error) {
branch = &model.AggregateBranches{}
err = pkgerr.WithStack(d.db.Model(&model.AggregateBranches{}).Where("project_id = ? AND branch_name = ? ", projectID, branchName).First(branch).Error)
return
}

View File

@@ -0,0 +1,280 @@
package dao
import (
"context"
"strconv"
"testing"
"go-common/app/admin/ep/saga/model"
"go-common/app/admin/ep/saga/service/utils"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoCommit(t *testing.T) {
convey.Convey("test dao commit", t, func(ctx convey.C) {
var (
commit *model.StatisticsCommits
err error
total int
)
ctx.Convey("When project not exist", func(ctx convey.C) {
total, err = d.HasCommit(55555, "55555")
ctx.Convey("HasCommit err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(total, convey.ShouldEqual, 0)
})
err = d.DelCommit(55555, "55555")
ctx.Convey("DelCommit err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
ctx.Convey("When project exist", func(ctx convey.C) {
var (
commitID = "11111"
PorjID = 11111
)
ctx.Convey("create commit", func(ctx convey.C) {
info := &model.StatisticsCommits{
CommitID: commitID,
ProjectID: PorjID,
Title: "test",
}
err = d.CreateCommit(info)
ctx.Convey("CreateCommit err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
ctx.Convey("has commit after create", func(ctx convey.C) {
total, err = d.HasCommit(PorjID, commitID)
ctx.Convey("HasCommit err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(total, convey.ShouldEqual, 1)
})
})
ctx.Convey("query commit", func(ctx convey.C) {
commit, err = d.QueryCommitByID(PorjID, commitID)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(commit.Title, convey.ShouldEqual, "test")
})
})
ctx.Convey("create second temp commit", func(ctx convey.C) {
temp := &model.StatisticsCommits{
CommitID: "33333",
ProjectID: 33333,
Title: "temp",
}
err = d.CreateCommit(temp)
ctx.Convey("CreateCommit again err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
ctx.Convey("has commit when exist two rows", func(ctx convey.C) {
total, err = d.HasCommit(PorjID, commitID)
ctx.Convey("HasCommit err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(total, convey.ShouldEqual, 1)
})
})
ctx.Convey("update commit", func(ctx convey.C) {
updateInfo := &model.StatisticsCommits{
CommitID: commitID,
ProjectID: PorjID,
Title: "update",
}
err = d.UpdateCommit(PorjID, commitID, updateInfo)
ctx.Convey("UpdateCommit err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
ctx.Convey("query commit after update", func(ctx convey.C) {
commit, err = d.QueryCommitByID(PorjID, commitID)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(commit.Title, convey.ShouldEqual, "update")
})
})
ctx.Convey("update temp commit", func(ctx convey.C) {
updateInfo := &model.StatisticsCommits{
CommitID: "33333",
ProjectID: 33333,
Title: "update temp",
}
err = d.UpdateCommit(33333, "33333", updateInfo)
ctx.Convey("UpdateCommit temp err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
ctx.Convey("query temp commit after update", func(ctx convey.C) {
commit, err = d.QueryCommitByID(33333, "33333")
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(commit.Title, convey.ShouldEqual, "update temp")
})
})
ctx.Convey("delete commit", func(ctx convey.C) {
err = d.DelCommit(PorjID, commitID)
ctx.Convey("DelCommit commit exist.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
ctx.Convey("has commit after delete", func(ctx convey.C) {
total, err = d.HasCommit(PorjID, commitID)
ctx.Convey("HasCommit after delete .", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(total, convey.ShouldEqual, 0)
})
})
ctx.Convey("has temp commit when not delete", func(ctx convey.C) {
total, err = d.HasCommit(33333, "33333")
ctx.Convey("HasCommit after delete .", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(total, convey.ShouldEqual, 1)
})
})
ctx.Convey("delete temp commit", func(ctx convey.C) {
err = d.DelCommit(33333, "33333")
ctx.Convey("DelCommit commit exist.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
ctx.Convey("has tem commit after delete", func(ctx convey.C) {
total, err = d.HasCommit(33333, "33333")
ctx.Convey("HasCommit after delete .", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(total, convey.ShouldEqual, 0)
})
})
})
})
}
func TestDaoCommitSpe(t *testing.T) {
convey.Convey("test dao commit include special char", t, func(ctx convey.C) {
var err error
commitSpe := &model.StatisticsCommits{
CommitID: "22222",
ProjectID: 22222,
Title: "🇭相关",
}
ctx.Convey("create commit", func(ctx convey.C) {
err = d.CreateCommit(commitSpe)
ctx.Convey("CreateCommit err should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldNotBeNil)
ctx.So(err.Error(), convey.ShouldContainSubstring, "Incorrect string value")
})
})
ctx.Convey("create commit after to ascii", func(ctx convey.C) {
commitSpe.Title = strconv.QuoteToASCII(commitSpe.Title)
err = d.CreateCommit(commitSpe)
ctx.Convey("CreateCommit err should be nil after to ascii.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
ctx.Convey("delete commit", func(ctx convey.C) {
err = d.DelCommit(22222, "22222")
ctx.Convey("DelCommit err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestQueryPipelinesByTime(t *testing.T) {
convey.Convey("QueryPipelinesByTime", t, func(ctx convey.C) {
var (
req = &model.PipelineDataReq{
Branch: "master",
State: "success",
User: "wangweizhen",
}
since = `2018-12-01 00:00:00`
until = `2018-12-02 00:00:00`
)
ctx.Convey("query pipelines info", func(ctx convey.C) {
total, statNum, _, err := d.QueryPipelinesByTime(682, req, since, until)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(total, convey.ShouldBeGreaterThan, 1)
ctx.So(statNum, convey.ShouldBeGreaterThan, 1)
})
})
})
}
func TestMRByProjectID(t *testing.T) {
convey.Convey("QueryMRByProjectID", t, func(ctx convey.C) {
since, until := utils.CalSyncTime()
ctx.Convey("query MRByProjectID info", func(ctx convey.C) {
mrs, err := d.MRByProjectID(context.TODO(), 682, since, until)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(len(mrs), convey.ShouldBeGreaterThan, 0)
})
})
})
}
func TestDaoBranchSpe(t *testing.T) {
convey.Convey("test dao branch include special char", t, func(ctx convey.C) {
var (
branch = &model.StatisticsBranches{
ProjectID: 666,
ProjectName: "六六大顺",
CommitID: "6661",
BranchName: "666",
Protected: false,
Merged: false,
DevelopersCanPush: false,
DevelopersCanMerge: false,
IsDeleted: true,
}
total int
err error
)
ctx.Convey("create branch", func(ctx convey.C) {
err = d.CreateBranch(context.TODO(), branch)
ctx.Convey("CreateBranch err should not be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
ctx.Convey("has branch", func(ctx convey.C) {
total, err = d.HasBranch(context.TODO(), 666, "666")
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(total, convey.ShouldBeGreaterThan, 0)
})
})
ctx.Convey("update branch", func(ctx convey.C) {
err = d.UpdateBranch(context.TODO(), 666, "666", branch)
ctx.Convey("Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,13 @@
package dao
import (
"go-common/app/admin/ep/saga/model"
pkgerr "github.com/pkg/errors"
)
// Tasks get all the tasks for the specified project
func (d *Dao) Tasks(projID, status int) (tasks []*model.Task, err error) {
err = pkgerr.WithStack(d.db.Where(&model.Task{ProjID: projID, Status: status}).Find(&tasks).Error)
return
}

View File

@@ -0,0 +1,208 @@
package dao
import (
"database/sql"
"fmt"
"strconv"
"strings"
"go-common/app/admin/ep/saga/model"
"go-common/library/log"
"github.com/pkg/errors"
pkgerr "github.com/pkg/errors"
)
const (
_where = "WHERE"
_and = "AND"
_wildcards = "%"
_contactRightJoinLogSQL = "SELECT m.id,m.name,ml.username,ml.operation_type,ml.operation_result,ml.ctime FROM machines AS m INNER JOIN machine_logs AS ml ON m.id = ml.machine_id"
_contactRightCountSQL = "SELECT count(m.id) FROM machines AS m INNER JOIN machine_logs AS ml ON m.id = ml.machine_id"
)
//var (
// regUserID = regexp.MustCompile(`^\d+$`)
//)
// QueryUserByUserName query user by user name
func (d *Dao) QueryUserByUserName(userName string) (contactInfo *model.ContactInfo, err error) {
contactInfo = &model.ContactInfo{}
err = pkgerr.WithStack(d.db.Where(&model.ContactInfo{UserName: userName}).First(contactInfo).Error)
return
}
// QueryUserByID query user by user ID
func (d *Dao) QueryUserByID(userID string) (contactInfo *model.ContactInfo, err error) {
contactInfo = &model.ContactInfo{}
err = pkgerr.WithStack(d.db.Where(&model.ContactInfo{UserID: userID}).First(contactInfo).Error)
return
}
// UserIds query user ids for the user names 从数据库表ContactInfo查询员工编号
func (d *Dao) UserIds(userNames []string) (userIds string, err error) {
var (
userName string
ids []string
contactInfo *model.ContactInfo
)
if len(userNames) == 0 {
err = errors.Errorf("UserIds: userNames is empty!")
return
}
for _, userName = range userNames {
if contactInfo, err = d.QueryUserByUserName(userName); err != nil {
err = errors.Wrapf(err, "UserIds: no such user (%s) in db, err (%s)", userName, err.Error())
return
}
log.Info("UserIds: username (%s), userid (%s)", userName, contactInfo.UserID)
if contactInfo.UserID != "" {
ids = append(ids, contactInfo.UserID)
}
}
if len(ids) <= 0 {
err = errors.Wrapf(err, "UserIds: failed to find all the users in db, what a pity!")
return
}
userIds = strings.Join(ids, "|")
return
}
// ContactInfos query all the records in contact_infos
func (d *Dao) ContactInfos() (contactInfos []*model.ContactInfo, err error) {
err = pkgerr.WithStack(d.db.Find(&contactInfos).Error)
return
}
// FindContacts find application record by auditor.
func (d *Dao) FindContacts(pn, ps int) (total int64, ars []*model.ContactInfo, err error) {
cdb := d.db.Model(&model.ContactInfo{}).Order("ID desc").Offset((pn - 1) * ps).Limit(ps)
if err = pkgerr.WithStack(cdb.Find(&ars).Error); err != nil {
return
}
if err = pkgerr.WithStack(d.db.Model(&model.ContactInfo{}).Count(&total).Error); err != nil {
return
}
return
}
// CreateContact create contact info record
func (d *Dao) CreateContact(contact *model.ContactInfo) (err error) {
return pkgerr.WithStack(d.db.Create(contact).Error)
}
// DelContact delete the contact info with the specified UserID
func (d *Dao) DelContact(contact *model.ContactInfo) (err error) {
return pkgerr.WithStack(d.db.Delete(contact).Error)
}
// UptContact update the contact information
func (d *Dao) UptContact(contact *model.ContactInfo) (err error) {
return pkgerr.WithStack(d.db.Model(&model.ContactInfo{}).Where(&model.ContactInfo{UserID: contact.UserID}).Updates(*contact).Error)
}
// InsertContactLog insert machine log.
func (d *Dao) InsertContactLog(contactlog *model.ContactLog) (err error) {
return pkgerr.WithStack(d.db.Create(contactlog).Error)
}
// FindMachineLogs Find Machine Logs.
func (d *Dao) FindMachineLogs(queryRequest *model.QueryContactLogRequest) (total int64, machineLogs []*model.AboundContactLog, err error) {
var (
qSQL = _contactRightJoinLogSQL
cSQL = _contactRightCountSQL
rows *sql.Rows
)
if queryRequest.UserID > 0 || queryRequest.UserName != "" || queryRequest.OperateType != "" || queryRequest.OperateUser != "" {
var (
strSQL = ""
logicalWord = _where
)
if queryRequest.UserID > 0 {
strSQL = fmt.Sprintf("%s %s ml.machine_id = %s", strSQL, logicalWord, strconv.FormatInt(queryRequest.UserID, 10))
logicalWord = _and
}
if queryRequest.UserName != "" {
strSQL = fmt.Sprintf("%s %s m.name like '%s'", strSQL, logicalWord, _wildcards+queryRequest.UserName+_wildcards)
logicalWord = _and
}
if queryRequest.OperateType != "" {
strSQL = fmt.Sprintf("%s %s ml.operation_type like '%s'", strSQL, logicalWord, _wildcards+queryRequest.OperateType+_wildcards)
logicalWord = _and
}
if queryRequest.OperateUser != "" {
strSQL = fmt.Sprintf("%s %s ml.username like '%s'", strSQL, logicalWord, _wildcards+queryRequest.OperateUser+_wildcards)
logicalWord = _and
}
qSQL = _contactRightJoinLogSQL + " " + strSQL
cSQL = _contactRightCountSQL + " " + strSQL
}
cDB := d.db.Raw(cSQL)
if err = pkgerr.WithStack(cDB.Count(&total).Error); err != nil {
return
}
gDB := d.db.Raw(qSQL)
if rows, err = gDB.Order("ml.ctime DESC").Offset((queryRequest.PageNum - 1) * queryRequest.PageSize).Limit(queryRequest.PageSize).Rows(); err != nil {
return
}
defer rows.Close()
for rows.Next() {
ml := &model.AboundContactLog{}
if err = rows.Scan(&ml.MachineID, &ml.Name, &ml.Username, &ml.OperateType, &ml.OperateResult, &ml.OperateTime); err != nil {
return
}
machineLogs = append(machineLogs, ml)
}
return
}
// AddWechatCreateLog ...
func (d *Dao) AddWechatCreateLog(req *model.WechatCreateLog) (err error) {
return pkgerr.WithStack(d.db.Create(req).Error)
}
// QueryWechatCreateLog ...
func (d *Dao) QueryWechatCreateLog(ifpage bool, req *model.Pagination, wechatCreateInfo *model.WechatCreateLog) (wechatCreateInfos []*model.WechatCreateLog, total int, err error) {
gDB := d.db.Table("wechat_create_logs").Where(wechatCreateInfo).Model(&model.WechatCreateLog{})
if err = errors.WithStack(gDB.Count(&total).Error); err != nil {
return
}
if ifpage {
gDB = gDB.Order("id DESC").Offset((req.PageNum - 1) * req.PageSize).Limit(req.PageSize).Find(&wechatCreateInfos)
} else {
gDB = gDB.Find(&wechatCreateInfos)
}
if gDB.Error != nil {
if gDB.RecordNotFound() {
err = nil
} else {
err = errors.WithStack(gDB.Error)
}
}
return
}
// CreateChatLog ...
func (d *Dao) CreateChatLog(req *model.WechatChatLog) (err error) {
return pkgerr.WithStack(d.db.Create(req).Error)
}
// CreateMessageLog ...
func (d *Dao) CreateMessageLog(req *model.WechatMessageLog) (err error) {
return pkgerr.WithStack(d.db.Create(req).Error)
}

View File

@@ -0,0 +1,122 @@
package dao
import (
"testing"
"go-common/app/admin/ep/saga/model"
"github.com/smartystreets/goconvey/convey"
)
func TestQueryUserByUserName(t *testing.T) {
convey.Convey("Test QueryUserByUserName", t, func(ctx convey.C) {
contactInfo, err := d.QueryUserByUserName("zhanglin")
ctx.Convey("The err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(contactInfo.NickName, convey.ShouldEqual, "木木哥")
ctx.So(contactInfo.UserID, convey.ShouldEqual, "003207")
})
})
}
func TestQueryUserByID(t *testing.T) {
convey.Convey("Test QueryUserByID", t, func(ctx convey.C) {
contactInfo, err := d.QueryUserByID("003207")
ctx.Convey("The err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(contactInfo.UserName, convey.ShouldEqual, "zhanglin")
ctx.So(contactInfo.NickName, convey.ShouldEqual, "木木哥")
})
})
}
func TestUserIds(t *testing.T) {
convey.Convey("Test UserIds", t, func(ctx convey.C) {
var (
userNames = []string{"zhanglin", "wuwei"}
)
userIds, err := d.UserIds(userNames)
ctx.Convey("The err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(userIds, convey.ShouldEqual, "003207|001396")
})
})
}
func TestWechatContact(t *testing.T) {
convey.Convey("test wechat contact", t, func(ctx convey.C) {
var (
ID = "111"
)
contact := &model.ContactInfo{
ID: ID,
UserName: "lisi",
UserID: "222",
NickName: "sansan",
VisibleSaga: true,
}
contactUpdate := &model.ContactInfo{
ID: ID,
UserName: "zhan",
UserID: "333",
NickName: "sansan",
VisibleSaga: true,
}
ctx.Convey("set contact", func(ctx convey.C) {
err := d.CreateContact(contact)
ctx.Convey("set err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
ctx.Convey("update contact", func(ctx convey.C) {
err := d.UptContact(contactUpdate)
ctx.Convey("get err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
ctx.Convey("delete contact", func(ctx convey.C) {
err := d.DelContact(contactUpdate)
ctx.Convey("get err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestWechatCreateLog(t *testing.T) {
convey.Convey("test wechat create log", t, func(ctx convey.C) {
info := &model.WechatCreateLog{
Name: "lisi",
Owner: "zhanglin",
ChatID: "333",
Cuser: "333",
}
info1 := &model.WechatCreateLog{
Name: "lisi",
}
ctx.Convey("set wechat log", func(ctx convey.C) {
err := d.AddWechatCreateLog(info)
ctx.Convey("set err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
ctx.Convey("query wechat log", func(ctx convey.C) {
infos, total, err := d.QueryWechatCreateLog(false, nil, info1)
ctx.Convey("query err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(total, convey.ShouldBeGreaterThanOrEqualTo, 1)
ctx.So(infos[0].Name, convey.ShouldEqual, "lisi")
})
})
})
}

View File

@@ -0,0 +1,279 @@
package dao
import (
"bytes"
"context"
"encoding/json"
"net/http"
xhttp "net/http"
"net/url"
"strconv"
"strings"
"go-common/app/admin/ep/saga/model"
"go-common/library/ecode"
"go-common/library/log"
"github.com/pkg/errors"
)
const (
qyWechatURL = "https://qyapi.weixin.qq.com"
corpID = "wx0833ac9926284fa5" // 企业微信Bilibili的企业ID
departmentID = "12" // 公司统一用部门ID
//corpID = "wwa24b497e5efdd78c" // 香满庭
//departmentID = "2" // 香满庭
_ajSessionID = "_AJSESSIONID"
)
// WechatPushMsg wechat push text message to specified user 发送企业微信信息
func (d *Dao) WechatPushMsg(c context.Context, token string, txtMsg *model.TxtNotification) (invalidUser string, err error) {
var (
u string
params = url.Values{}
res struct {
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
InvalidUser string `json:"invaliduser"`
InvalidParty string `json:"invalidparty"`
InvalidTag string `json:"invalidtag"`
}
)
u = qyWechatURL + "/cgi-bin/message/send"
params.Set("access_token", token)
if err = d.PostJSON(c, u, "", params, &res, txtMsg); err != nil {
log.Info("WechatPushMsg PostJSON err (%+v)", err)
return
}
if res.ErrCode != 0 || res.InvalidUser != "" || res.InvalidParty != "" || res.InvalidTag != "" {
invalidUser = res.InvalidUser
err = errors.Errorf("WechatPushMsg: errcode: %d, errmsg: %s, invalidUser: %s, invalidParty: %s, invalidTag: %s",
res.ErrCode, res.ErrMsg, res.InvalidUser, res.InvalidParty, res.InvalidTag)
log.Info("WechatPushMsg err (%+v)", err)
return
}
return
}
// PostJSON post http request with json params.
func (d *Dao) PostJSON(c context.Context, uri, ip string, params url.Values, res interface{}, v interface{}) (err error) {
var (
body = &bytes.Buffer{}
req *xhttp.Request
url string
en string
)
if err = json.NewEncoder(body).Encode(v); err != nil {
return
}
url = uri
if en = params.Encode(); en != "" {
url += "?" + en
}
if req, err = xhttp.NewRequest(xhttp.MethodPost, url, body); err != nil {
return
}
if err = d.httpClient.Do(c, req, &res); err != nil {
return
}
return
}
// WechatAccessToken query access token with the specified secret 企业微信api获取公司token
func (d *Dao) WechatAccessToken(c context.Context, secret string) (token string, expire int32, err error) {
var (
u string
params = url.Values{}
res struct {
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
AccessToken string `json:"access_token"`
ExpiresIn int32 `json:"expires_in"`
}
)
u = qyWechatURL + "/cgi-bin/gettoken"
params.Set("corpid", corpID)
params.Set("corpsecret", secret)
if err = d.httpClient.Get(c, u, "", params, &res); err != nil {
return
}
if res.ErrCode != 0 {
err = errors.Errorf("WechatAccessToken: errcode: %d, errmsg: %s", res.ErrCode, res.ErrMsg)
return
}
token = res.AccessToken
expire = res.ExpiresIn
return
}
// WechatContacts query all the contacts
func (d *Dao) WechatContacts(c context.Context, accessToken string) (contacts []*model.ContactInfo, err error) {
var (
u string
params = url.Values{}
res struct {
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
UserList []*model.ContactInfo `json:"userlist"`
}
)
u = qyWechatURL + "/cgi-bin/user/list"
params.Set("access_token", accessToken)
params.Set("department_id", departmentID)
params.Set("fetch_child", "1")
if err = d.httpClient.Get(c, u, "", params, &res); err != nil {
return
}
if res.ErrCode != 0 {
err = errors.Errorf("WechatContacts: errcode: %d, errmsg: %s", res.ErrCode, res.ErrMsg)
return
}
contacts = res.UserList
return
}
// WechatSagaVisible get all the user ids who can visiable saga 获取用户ID列表
func (d *Dao) WechatSagaVisible(c context.Context, accessToken string, agentID int) (users []*model.UserInfo, err error) {
var (
u string
params = url.Values{}
res struct {
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
VisibleUsers model.AllowUserInfo `json:"allow_userinfos"`
}
)
u = qyWechatURL + "/cgi-bin/agent/get"
params.Set("access_token", accessToken)
params.Set("agentid", strconv.Itoa(agentID))
if err = d.httpClient.Get(c, u, "", params, &res); err != nil {
return
}
if res.ErrCode != 0 {
err = errors.Errorf("WechatSagaVisible: errcode: %d, errmsg: %s", res.ErrCode, res.ErrMsg)
return
}
users = res.VisibleUsers.Users
return
}
// WechatParams ...
func (d *Dao) WechatParams(c context.Context, u string, params url.Values, resp interface{}) (err error) {
var (
req *xhttp.Request
en string
)
if en = params.Encode(); en != "" {
u += "?" + en
}
if req, err = xhttp.NewRequest(xhttp.MethodGet, u, nil); err != nil {
return
}
return d.httpClient.Do(c, req, &resp)
}
// NewRequest ...
func (d *Dao) NewRequest(method, url string, v interface{}) (req *http.Request, err error) {
body := &bytes.Buffer{}
if method != http.MethodGet {
if err = json.NewEncoder(body).Encode(v); err != nil {
log.Error("json encode value(%s), error(%v) ", v, err)
return
}
}
if req, err = http.NewRequest(method, url, body); err != nil {
log.Error("http new request url(%s), error(%v)", url, err)
}
return
}
// QueryAllConfigFile ...
func (d *Dao) QueryAllConfigFile(c context.Context, sessionID, url string) (resp *model.ConfigData, err error) {
var (
req *http.Request
respValue = &model.SvenResp{}
)
log.Info("QueryAllConfigFile: sessionID: %s, url: %s", sessionID, url)
if req, err = d.NewRequest(http.MethodGet, url, nil); err != nil {
return
}
req.Header.Set("Cookie", _ajSessionID+"="+sessionID)
if err = d.httpClient.Do(c, req, &respValue); err != nil {
return
}
if respValue.Code != ecode.OK.Code() {
err = errors.Wrapf(ecode.Int(respValue.Code), "QueryAllConfigFile failed, sessionID(%s), url(%s)", sessionID, url)
return
}
resp = respValue.Data
return
}
// QueryConfigFileContent ...
func (d *Dao) QueryConfigFileContent(c context.Context, sessionID, url string) (content string, err error) {
var (
req *http.Request
respValue = &model.ConfigValueResp{}
)
if req, err = d.NewRequest(http.MethodGet, url, nil); err != nil {
return
}
req.Header.Set("Cookie", _ajSessionID+"="+sessionID)
if err = d.httpClient.Do(c, req, respValue); err != nil {
return
}
if respValue.Code != ecode.OK.Code() {
err = errors.Wrapf(ecode.Int(respValue.Code), "QueryConfigFileContent failed, sessionID(%s), url(%s)", sessionID, url)
return
}
if respValue.Data != nil {
content = respValue.Data.Comment
}
return
}
// RequestConfig ...
func (d *Dao) RequestConfig(c context.Context, sessionID, reqUrl string, params url.Values) (resp *model.CommonResp, err error) {
var req *http.Request
if req, err = http.NewRequest("POST", reqUrl, strings.NewReader(params.Encode())); err != nil {
log.Error("http.NewRequest error(%v) | uri(%s) params(%s)", err, reqUrl, params.Encode())
return
}
log.Info("RequestConfig url: %v", req.URL)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Cookie", _ajSessionID+"="+sessionID)
if err = d.httpClient.Do(c, req, &resp); err != nil {
log.Error("RequestConfig err%+v", err)
return
}
if resp.Code != ecode.OK.Code() {
err = errors.Wrapf(ecode.Int(resp.Code), "RequestConfig failed, sessionID(%s), url(%s)", sessionID, reqUrl)
return
}
return
}

120
app/admin/ep/saga/dao/mc.go Normal file
View File

@@ -0,0 +1,120 @@
package dao
import (
"context"
"go-common/app/admin/ep/saga/model"
"go-common/library/cache/memcache"
"github.com/pkg/errors"
)
func (d *Dao) pingMC(c context.Context) (err error) {
conn := d.mc.Get(c)
defer conn.Close()
if err = conn.Set(&memcache.Item{Key: "ping", Value: []byte{1}, Expiration: 0}); err != nil {
err = errors.Wrap(err, "conn.Store(set,ping,1)")
}
return
}
// SetData set data info to memcache
func (d *Dao) SetData(c context.Context, key string, dataMap map[string]*model.TeamDataResp) (err error) {
var (
conn = d.mc.Get(c)
item *memcache.Item
//dataMap = make(map[string]*model.TeamDataResp)
)
defer conn.Close()
item = &memcache.Item{Key: key, Object: dataMap, Expiration: 0, Flags: memcache.FlagJSON}
if err = conn.Set(item); err != nil {
err = errors.Wrapf(err, "conn.Set(%s,%v)", key, dataMap)
return
}
return
}
// GetData get data info from memcache
func (d *Dao) GetData(c context.Context, key string, dataMap *map[string]*model.TeamDataResp) (err error) {
var (
conn = d.mc.Get(c)
reply *memcache.Item
)
defer conn.Close()
reply, err = conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
return
}
err = errors.Wrapf(err, "conn.Get(%s)", key)
return
}
if err = conn.Scan(reply, dataMap); err != nil {
err = errors.Wrapf(err, "reply.Scan(%s)", string(reply.Value))
return
}
return
}
// DeleteData delete data info in memcache
func (d *Dao) DeleteData(c context.Context, key string) (err error) {
var (
conn = d.mc.Get(c)
)
defer conn.Close()
err = conn.Delete(key)
if err != nil {
if err == memcache.ErrNotFound {
err = nil
return
}
err = errors.Wrapf(err, "conn.Delete(%s)", key)
}
return
}
// SetPipeline set pipeline info info to memcache
func (d *Dao) SetPipeline(c context.Context, key string, pipeline *model.PipelineDataResp) (err error) {
var (
conn = d.mc.Get(c)
item *memcache.Item
)
defer conn.Close()
item = &memcache.Item{Key: key, Object: pipeline, Expiration: 0, Flags: memcache.FlagJSON}
if err = conn.Set(item); err != nil {
err = errors.Wrapf(err, "conn.Set(%s,%v)", key, pipeline)
return
}
return
}
// GetPipeline get pipeline info from memcache
func (d *Dao) GetPipeline(c context.Context, key string) (pipeline *model.PipelineDataResp, err error) {
var (
conn = d.mc.Get(c)
reply *memcache.Item
)
defer conn.Close()
reply, err = conn.Get(key)
if err != nil {
if err == memcache.ErrNotFound {
return
}
err = errors.Wrapf(err, "conn.Get(%s,%v)", key, pipeline)
return
}
pipeline = new(model.PipelineDataResp)
if err = conn.Scan(reply, pipeline); err != nil {
err = errors.Wrapf(err, "reply.Scan(%s)", string(reply.Value))
}
return
}

View File

@@ -0,0 +1,100 @@
package dao
import (
"context"
"testing"
"go-common/app/admin/ep/saga/model"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoPingMC(t *testing.T) {
convey.Convey("pingMC", t, func(ctx convey.C) {
var (
c = context.Background()
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.pingMC(c)
ctx.Convey("The err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestMcData(t *testing.T) {
convey.Convey("test mc data", t, func(ctx convey.C) {
var (
c = context.Background()
key = "111"
dataSet = make(map[string]*model.TeamDataResp)
dataGet = make(map[string]*model.TeamDataResp)
)
teamData := &model.TeamDataResp{
Department: "live",
Business: "ios",
QueryDes: "description",
Total: 10,
}
dataSet["zhangsan"] = teamData
ctx.Convey("set data", func(ctx convey.C) {
err := d.SetData(c, key, dataSet)
ctx.Convey("set err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
ctx.Convey("get data", func(ctx convey.C) {
err := d.GetData(c, key, &dataGet)
_, ok := dataGet["zhangsan"]
ctx.Convey("get err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(ok, convey.ShouldEqual, true)
ctx.So(dataGet["zhangsan"].Department, convey.ShouldEqual, "live")
ctx.So(dataGet["zhangsan"].Total, convey.ShouldEqual, 10)
})
})
ctx.Convey("delete data", func(ctx convey.C) {
err := d.DeleteData(c, key)
_, ok := dataGet["zhangsan"]
ctx.Convey("get err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(ok, convey.ShouldEqual, false)
})
})
})
}
func TestMcPipeline(t *testing.T) {
convey.Convey("test mc Pipeline", t, func(ctx convey.C) {
var (
c = context.Background()
key = "111"
)
pipelineData := &model.PipelineDataResp{
Department: "openplatform",
Business: "android",
QueryDes: "description",
Total: 11,
}
ctx.Convey("set pipeline data", func(ctx convey.C) {
err := d.SetPipeline(c, key, pipelineData)
ctx.Convey("set err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
ctx.Convey("get pipeline data", func(ctx convey.C) {
pipeline, err := d.GetPipeline(c, key)
ctx.Convey("get err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(pipeline.Business, convey.ShouldEqual, "android")
ctx.So(pipeline.Total, convey.ShouldEqual, 11)
})
})
})
}

View File

@@ -0,0 +1,195 @@
package dao
import (
"context"
"encoding/json"
"fmt"
"go-common/app/admin/ep/saga/model"
"go-common/library/cache/redis"
"go-common/library/log"
"github.com/pkg/errors"
)
const (
requiredViableUsersKeyRedis = "saga_wechat_require_visible_users_key"
)
func (d *Dao) pingRedis(c context.Context) (err error) {
conn := d.redis.Get(c)
_, err = conn.Do("SET", "PING", "PONG")
defer conn.Close()
return
}
func weixinTokenKeyRedis(key string) string {
return fmt.Sprintf("saga_weixin_token_%s", key)
}
// AccessTokenRedis get access token from redis
func (d *Dao) AccessTokenRedis(c context.Context, key string) (token string, err error) {
var (
wkey = weixinTokenKeyRedis(key)
conn = d.redis.Get(c)
value []byte
)
defer conn.Close()
if value, err = redis.Bytes(conn.Do("GET", wkey)); err != nil {
if err == redis.ErrNil {
err = nil
}
return
}
if err = json.Unmarshal(value, &token); err != nil {
err = errors.WithStack(err)
return
}
return
}
// SetAccessTokenRedis set the access token to redis
func (d *Dao) SetAccessTokenRedis(c context.Context, key, token string, expire int32) (err error) {
var (
wkey = weixinTokenKeyRedis(key)
conn = d.redis.Get(c)
item []byte
)
defer conn.Close()
if item, err = json.Marshal(token); err != nil {
err = errors.WithStack(err)
return
}
if err = conn.Send("SET", wkey, item, "EX", expire); err != nil {
return
}
if err = conn.Flush(); err != nil {
return
}
if _, err = conn.Receive(); err != nil {
return
}
return
}
// RequireVisibleUsersRedis get wechat require visible users from redis
func (d *Dao) RequireVisibleUsersRedis(c context.Context, userMap *map[string]model.RequireVisibleUser) (err error) {
var (
conn = d.redis.Get(c)
reply []byte
)
defer conn.Close()
if reply, err = redis.Bytes(conn.Do("GET", requiredViableUsersKeyRedis)); err != nil {
if err == redis.ErrNil {
log.Info("no such key (%s) in cache, err (%s)", requiredViableUsersKeyRedis, err.Error())
err = nil
}
return
}
if err = json.Unmarshal(reply, &userMap); err != nil {
err = errors.WithStack(err)
return
}
return
}
// SetRequireVisibleUsersRedis set wechat require visible users to redis
func (d *Dao) SetRequireVisibleUsersRedis(c context.Context, contactInfo *model.ContactInfo) (err error) {
var (
conn = d.redis.Get(c)
item []byte
userMap = make(map[string]model.RequireVisibleUser)
)
defer conn.Close()
if err = d.RequireVisibleUsersRedis(c, &userMap); err != nil {
log.Error("get require visible user error(%v)", err)
return
}
user := model.RequireVisibleUser{
UserName: contactInfo.UserName,
NickName: contactInfo.NickName,
}
userMap[contactInfo.UserID] = user
if item, err = json.Marshal(userMap); err != nil {
err = errors.WithStack(err)
return
}
if err = conn.Send("SET", requiredViableUsersKeyRedis, item); err != nil {
return
}
if err = conn.Flush(); err != nil {
return
}
if _, err = conn.Receive(); err != nil {
err = errors.Wrapf(err, "conn.Set(%s,%v)", requiredViableUsersKeyRedis, userMap)
return
}
return
}
// DeleteRequireVisibleUsersRedis delete the wechat require visible key in redis
func (d *Dao) DeleteRequireVisibleUsersRedis(c context.Context) (err error) {
var (
conn = d.redis.Get(c)
)
defer conn.Close()
if err = conn.Send("DEL", requiredViableUsersKeyRedis); err != nil {
return
}
if err = conn.Flush(); err != nil {
return
}
if _, err = conn.Receive(); err != nil {
err = errors.Wrapf(err, "conn.Delete(%s)", requiredViableUsersKeyRedis)
return
}
return
}
// SetItemRedis ...
func (d *Dao) SetItemRedis(c context.Context, key string, value interface{}, ttl int) (err error) {
var (
conn = d.redis.Get(c)
bs []byte
)
defer conn.Close()
if bs, err = json.Marshal(value); err != nil {
return errors.WithStack(err)
}
if ttl == 0 {
if _, err = conn.Do("SET", key, bs); err != nil {
return errors.Wrapf(err, "conn.Do(SET %s, %s) error(%v)", key, value, err)
}
} else {
if _, err = conn.Do("SET", key, bs, "EX", ttl); err != nil {
return errors.Wrapf(err, "conn.Do(SET %s, %s) error(%v)", key, value, err)
}
}
return
}
// ItemRedis ...
func (d *Dao) ItemRedis(c context.Context, key string, value interface{}) (err error) {
var (
conn = d.redis.Get(c)
bs []byte
)
defer conn.Close()
if bs, err = redis.Bytes(conn.Do("GET", key)); err != nil {
if err == redis.ErrNil {
return
}
return errors.Wrapf(err, "conn.Get(%s)", key)
}
if err = json.Unmarshal(bs, &value); err != nil {
return errors.WithStack(err)
}
return
}

View File

@@ -0,0 +1,116 @@
package dao
import (
"context"
"testing"
"go-common/app/admin/ep/saga/model"
"github.com/smartystreets/goconvey/convey"
)
func TestDaoWeixinTokenKeyRedis(t *testing.T) {
convey.Convey("weixinTokenKeyRedis", t, func(ctx convey.C) {
var (
key = "111"
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
p1 := weixinTokenKeyRedis(key)
ctx.Convey("Then p1 should not be nil.", func(ctx convey.C) {
ctx.So(p1, convey.ShouldEqual, "saga_weixin_token__111")
})
})
})
}
func TestDaoAccessTokenRedis(t *testing.T) {
convey.Convey("AccessTokenRedis", t, func(ctx convey.C) {
var (
c = context.Background()
key = "111"
token = "sdfgsdgfdg"
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.SetAccessTokenRedis(c, key, token, -1)
ctx.Convey("Set Access Token. Then err should be nil. ", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
token, err := d.AccessTokenRedis(c, key)
ctx.Convey("get access token. Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(token, convey.ShouldEqual, token)
})
})
})
}
func TestDaoRequireVisibleUsersRedis(t *testing.T) {
convey.Convey("RequireVisibleUsersRedis", t, func(ctx convey.C) {
var (
c = context.Background()
userID = "222"
contactInfo = &model.ContactInfo{
ID: "111",
UserName: "zhanglin",
UserID: userID,
NickName: "mumuge",
VisibleSaga: true,
}
userMap = make(map[string]model.RequireVisibleUser)
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.SetRequireVisibleUsersRedis(c, contactInfo)
ctx.Convey("Set Visible Users. Then err should be nil. ", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
err = d.RequireVisibleUsersRedis(c, &userMap)
ctx.Convey("get Visible Users. Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(userMap[userID].UserName, convey.ShouldEqual, "zhanglin")
ctx.So(userMap[userID].NickName, convey.ShouldEqual, "mumuge")
})
err = d.DeleteRequireVisibleUsersRedis(c)
ctx.Convey("delete Visible Users. Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestDaoGetItemRedis(t *testing.T) {
convey.Convey("GetItemRedis", t, func(ctx convey.C) {
var (
c = context.Background()
key = "333"
contactInfo = &model.ContactInfo{
ID: "111",
UserName: "zhanglin",
UserID: "333",
NickName: "mumuge",
VisibleSaga: true,
}
getContactInfo *model.ContactInfo
)
ctx.Convey("When everything gose positive", func(ctx convey.C) {
err := d.SetItemRedis(c, key, contactInfo, 0)
ctx.Convey("Set Item. Then err should be nil. ", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
})
err = d.ItemRedis(c, key, &getContactInfo)
ctx.Convey("get Item. Then err should be nil.", func(ctx convey.C) {
ctx.So(err, convey.ShouldBeNil)
ctx.So(getContactInfo.UserName, convey.ShouldEqual, "zhanglin")
ctx.So(getContactInfo.UserID, convey.ShouldEqual, "333")
ctx.So(getContactInfo.NickName, convey.ShouldEqual, "mumuge")
ctx.So(getContactInfo.VisibleSaga, convey.ShouldEqual, true)
})
})
})
}

View File

@@ -0,0 +1,54 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"basic.go",
"branch.go",
"commit.go",
"config.go",
"http.go",
"job.go",
"member.go",
"mr.go",
"pipeline.go",
"project.go",
"runner.go",
"task.go",
"team.go",
"user.go",
"wechat.go",
],
importpath = "go-common/app/admin/ep/saga/http",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/admin/ep/saga/conf:go_default_library",
"//app/admin/ep/saga/model:go_default_library",
"//app/admin/ep/saga/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/binding:go_default_library",
"//library/net/http/blademaster/middleware/permit: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,38 @@
package http
import (
"go-common/app/admin/ep/saga/model"
bm "go-common/library/net/http/blademaster"
)
func queryProjectStatus(c *bm.Context) {
var (
req = &model.ProjectDataReq{}
err error
)
if err = c.Bind(req); err != nil {
return
}
if req.Username, err = getUsername(c); err != nil {
c.JSON(nil, err)
return
}
c.JSON(srv.QueryProjectStatus(c, req), nil)
}
func queryProjectTypes(c *bm.Context) {
var (
req = &model.ProjectDataReq{}
err error
)
if err = c.Bind(req); err != nil {
return
}
if req.Username, err = getUsername(c); err != nil {
c.JSON(nil, err)
return
}
c.JSON(srv.QueryProjectTypes(c, req), nil)
}

View File

@@ -0,0 +1,40 @@
package http
import (
"go-common/app/admin/ep/saga/model"
bm "go-common/library/net/http/blademaster"
)
func queryProjectBranchList(c *bm.Context) {
var (
req = &model.ProjectDataReq{}
err error
)
if err = c.Bind(req); err != nil {
return
}
if req.Username, err = getUsername(c); err != nil {
c.JSON(nil, err)
return
}
c.JSON(srv.QueryProjectBranchList(c, req))
}
// @params queryBranchDiffWith
// @router get /ep/admin/saga/v1/data/branch/report
// @response BranchDiffWithRequest
func queryBranchDiffWith(c *bm.Context) {
var (
req = &model.BranchDiffWithRequest{}
err error
)
if err = c.Bind(req); err != nil {
return
}
if req.Username, err = getUsername(c); err != nil {
return
}
c.JSON(srv.QueryBranchDiffWith(c, req))
}

View File

@@ -0,0 +1,63 @@
package http
import (
"go-common/app/admin/ep/saga/model"
bm "go-common/library/net/http/blademaster"
)
// @params ProjectDataReq
// @router get /ep/admin/saga/v1/data/project/report
// @response ProjectDataResp
func queryProjectCommit(c *bm.Context) {
var (
req = &model.ProjectDataReq{}
err error
)
if err = c.Bind(req); err != nil {
return
}
if req.Username, err = getUsername(c); err != nil {
c.JSON(nil, err)
return
}
c.JSON(srv.QueryProjectCommit(c, req))
}
// @params TeamDataRequest
// @router get /ep/admin/saga/v1/data/commit/report
// @response TeamDataResp
func queryTeamCommit(c *bm.Context) {
var (
req = &model.TeamDataRequest{}
err error
)
if err = c.Bind(req); err != nil {
return
}
if req.Username, err = getUsername(c); err != nil {
c.JSON(nil, err)
return
}
c.JSON(srv.QueryTeamCommit(c, req))
}
// @params CommitRequest
// @router get /ep/admin/saga/v1/data/commit
// @response CommitResp
func queryCommit(c *bm.Context) {
var (
req = &model.CommitRequest{}
err error
)
if err = c.Bind(req); err != nil {
return
}
if req.Username, err = getUsername(c); err != nil {
c.JSON(nil, err)
return
}
c.JSON(srv.QueryCommit(c, req))
}

View File

@@ -0,0 +1,101 @@
package http
import (
"strconv"
"go-common/app/admin/ep/saga/model"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/binding"
)
func sagaUserList(c *bm.Context) {
c.JSON(srv.SagaUserList(c))
}
func runnerConfig(c *bm.Context) {
session, err := c.Request.Cookie("_AJSESSIONID")
if err != nil {
c.JSON(nil, err)
return
}
c.JSON(srv.QueryAllConfigFile(c, session.Value, false))
}
func sagaConfig(c *bm.Context) {
session, err := c.Request.Cookie("_AJSESSIONID")
if err != nil {
c.JSON(nil, err)
return
}
c.JSON(srv.QueryAllConfigFile(c, session.Value, true))
}
func publicSagaConfig(c *bm.Context) {
req := new(model.TagUpdate)
if err := c.Bind(req); err != nil {
return
}
session, err := c.Request.Cookie("_AJSESSIONID")
if err != nil {
c.JSON(nil, err)
return
}
var user string
if user, err = getUsername(c); err != nil {
c.JSON(nil, err)
return
}
c.JSON(srv.PublicConfig(c, session.Value, user, req.Names, req.Mark, true))
}
func existConfigSaga(c *bm.Context) {
var (
err error
projectID int
)
if projectID, err = strconv.Atoi(c.Request.Form.Get("project_id")); err != nil {
return
}
session, err := c.Request.Cookie("_AJSESSIONID")
if err != nil {
c.JSON(nil, err)
return
}
c.JSON(srv.QueryProjectSagaConfig(c, session.Value, projectID))
}
func releaseSagaConfig(c *bm.Context) {
var (
err error
user string
)
v := new(model.ConfigList)
if err = c.BindWith(v, binding.JSON); err != nil {
return
}
if user, err = getUsername(c); err != nil {
c.JSON(nil, err)
return
}
session, err := c.Request.Cookie("_AJSESSIONID")
if err != nil {
c.JSON(nil, err)
return
}
c.JSON(srv.ReleaseSagaConfig(c, user, session.Value, v))
}
func optionSaga(c *bm.Context) {
session, err := c.Request.Cookie("_AJSESSIONID")
if err != nil {
c.JSON(nil, err)
return
}
projectID := c.Request.Form.Get("project_id")
log.Info("=====optionSaga projectID: %s", projectID)
c.JSON(srv.OptionSaga(c, projectID, session.Value))
}

View File

@@ -0,0 +1,137 @@
package http
import (
"net/http"
"go-common/app/admin/ep/saga/conf"
"go-common/app/admin/ep/saga/service"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/middleware/permit"
)
var (
srv *service.Service
authSvc *permit.Permit
)
// Init init
func Init(s *service.Service) {
srv = s
authSvc = permit.New2(nil)
engine := bm.DefaultServer(conf.Conf.BM)
engine.Ping(ping)
initRouter(engine)
if err := engine.Start(); err != nil {
log.Error("engine.Start error(%v)", err)
panic(err)
}
}
// initRouter init outer router api path.
func initRouter(e *bm.Engine) {
version := e.Group("/ep/admin/saga/v1", authSvc.Permit2(""))
{
project := version.Group("/projects")
{
project.GET("/favorite", favoriteProjects)
project.POST("/favorite/edit", editFavorite)
project.GET("/common", queryCommonProjects)
}
tasks := version.Group("/tasks")
{
tasks.GET("/project", projectTasks)
}
user := version.Group("/user")
{
user.GET("/query", queryUserInfo)
}
data := version.Group("/data")
{
data.GET("/teams", queryTeams)
data.GET("/project", queryProjectInfo)
data.GET("/project/commit", queryProjectCommit)
data.GET("/project/mr", queryProjectMr)
data.GET("/commit", queryCommit) // ignore
data.GET("/commit/report", queryTeamCommit)
data.GET("/mr/report", queryTeamMr)
data.GET("/pipeline/report", queryTeamPipeline)
data.GET("/project/pipelines", queryProjectPipelineLists)
data.GET("/project/branch", queryProjectBranchList)
data.GET("/project/members", queryProjectMembers)
data.GET("/project/status", queryProjectStatus)
data.GET("/project/query/types", queryProjectTypes)
data.GET("/project/runners", queryProjectRunners)
data.GET("/job/report", queryProjectJob)
data.GET("/project/mr/report", queryProjectMrReport)
data.GET("/branch/report", queryBranchDiffWith)
}
config := version.Group("/config")
{
config.GET("/whitelist", sagaUserList)
//get runner sven all config files
config.GET("", runnerConfig)
//get saga sven all config files
config.GET("/saga", sagaConfig)
config.GET("/exist/saga", existConfigSaga)
//public saga config
config.POST("/tag/update", publicSagaConfig)
//update and public saga config
config.POST("/update/now/saga", releaseSagaConfig)
//get current saga config
config.GET("/option/saga", optionSaga)
}
// V1 wechat will carry cookie
wechat := version.Group("/wechat")
{
wechat.GET("", queryContacts)
contactLog := wechat.Group("/log")
{
contactLog.GET("/query", queryContactLogs)
}
redisdata := version.Group("/redisdata")
{
redisdata.GET("/query", queryRedisdata)
}
wechat.GET("/analysis/contacts", syncWechatContacts)
wechat.POST("/appchat/create", createWechat)
wechat.GET("/appchat/create/log", queryWechatCreateLog)
wechat.GET("/appchat/get", getWechat)
wechat.POST("/appchat/send", sendGroupWechat)
wechat.POST("/message/send", sendWechat)
wechat.POST("/appchat/update", updateWechat)
}
}
version1 := e.Group("/ep/admin/saga/v2")
{
// V2 wechat will not carry cookie
wechat := version1.Group("/wechat")
{
wechat.POST("/appchat/create", createWechat)
wechat.GET("/appchat/create/log", queryWechatCreateLog)
wechat.GET("/appchat/get", getWechat)
wechat.POST("/appchat/send", sendGroupWechat)
wechat.POST("/message/send", sendWechat)
wechat.POST("/appchat/update", updateWechat)
}
}
}
func ping(c *bm.Context) {
if err := srv.Ping(c); err != nil {
log.Error("ping error(%v)", err)
c.AbortWithStatus(http.StatusServiceUnavailable)
}
}

View File

@@ -0,0 +1,26 @@
package http
import (
"go-common/app/admin/ep/saga/model"
bm "go-common/library/net/http/blademaster"
)
// @params queryProjectJob
// @router get /ep/admin/saga/v1/data/project/job
// @response TeamDataResp
func queryProjectJob(c *bm.Context) {
var (
req = &model.ProjectJobRequest{}
err error
)
if err = c.Bind(req); err != nil {
return
}
if req.Username, err = getUsername(c); err != nil {
c.JSON(nil, err)
return
}
//c.JSON(srv.QueryProjectJob(c, req))
c.JSON(srv.QueryProjectJobNew(c, req))
}

View File

@@ -0,0 +1,22 @@
package http
import (
"go-common/app/admin/ep/saga/model"
bm "go-common/library/net/http/blademaster"
)
func queryProjectMembers(c *bm.Context) {
var (
req = &model.ProjectDataReq{}
err error
)
if err = c.Bind(req); err != nil {
return
}
if req.Username, err = getUsername(c); err != nil {
c.JSON(nil, err)
return
}
c.JSON(srv.QueryProjectMembers(c, req))
}

View File

@@ -0,0 +1,62 @@
package http
import (
"go-common/app/admin/ep/saga/model"
bm "go-common/library/net/http/blademaster"
)
// @params ProjectDataReq
// @router get /ep/admin/saga/v1/data/project/report
// @response ProjectDataResp
func queryProjectMr(c *bm.Context) {
var (
req = &model.ProjectDataReq{}
err error
)
if err = c.Bind(req); err != nil {
return
}
if req.Username, err = getUsername(c); err != nil {
c.JSON(nil, err)
return
}
c.JSON(srv.QueryProjectMr(c, req))
}
// @params TeamDataRequest
// @router get /ep/admin/saga/v1/data/mr/report
// @response TeamDataResp
func queryTeamMr(c *bm.Context) {
var (
req = &model.TeamDataRequest{}
err error
)
if err = c.Bind(req); err != nil {
return
}
if req.Username, err = getUsername(c); err != nil {
c.JSON(nil, err)
return
}
c.JSON(srv.QueryTeamMr(c, req))
}
//@params ProjectMrReportReq
//@router get /ep/admin/saga/v1/data/project/mr/report
//@response ProjectMrReportResp
func queryProjectMrReport(c *bm.Context) {
var (
req = &model.ProjectMrReportReq{}
err error
)
if err = c.Bind(req); err != nil {
return
}
if req.Username, err = getUsername(c); err != nil {
c.JSON(nil, err)
return
}
c.JSON(srv.QueryProjectMrReport(c, req))
}

View File

@@ -0,0 +1,41 @@
package http
import (
"go-common/app/admin/ep/saga/model"
bm "go-common/library/net/http/blademaster"
)
// @params TeamDataRequest
// @router get /ep/admin/saga/v1/data/pipeline/report
// @response TeamDataResp
func queryTeamPipeline(c *bm.Context) {
var (
req = &model.TeamDataRequest{}
err error
)
if err = c.Bind(req); err != nil {
return
}
if req.Username, err = getUsername(c); err != nil {
c.JSON(nil, err)
return
}
c.JSON(srv.QueryTeamPipeline(c, req))
}
func queryProjectPipelineLists(c *bm.Context) {
var (
req = &model.PipelineDataReq{}
err error
)
if err = c.Bind(req); err != nil {
return
}
if req.Username, err = getUsername(c); err != nil {
c.JSON(nil, err)
return
}
c.JSON(srv.QueryProjectPipelineNew(c, req))
}

View File

@@ -0,0 +1,69 @@
package http
import (
"go-common/app/admin/ep/saga/model"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/binding"
)
// @params EmptyReq
// @router get /ep/admin/saga/v1/projects/favorite
// @response FavoriteProjectsResp
func favoriteProjects(ctx *bm.Context) {
var (
req = &model.Pagination{}
err error
userName string
)
if err = ctx.Bind(req); err != nil {
ctx.JSON(nil, err)
return
}
if userName, err = getUsername(ctx); err != nil {
ctx.JSON(nil, err)
return
}
ctx.JSON(srv.FavoriteProjects(ctx, req, userName))
}
// @params EditFavoriteReq
// @router post /ep/admin/saga/v1/projects/favorite/edit
// @response EmptyResp
func editFavorite(ctx *bm.Context) {
var (
req = &model.EditFavoriteReq{}
err error
userName string
)
if err = ctx.BindWith(req, binding.JSON); err != nil {
ctx.JSON(nil, err)
return
}
if userName, err = getUsername(ctx); err != nil {
ctx.JSON(nil, err)
return
}
ctx.JSON(srv.EditFavorite(ctx, req, userName))
}
func queryCommonProjects(ctx *bm.Context) {
ctx.JSON(srv.QueryCommonProjects(ctx))
}
// @params QueryProjectInfoRequest
// @router get /ep/admin/saga/v1/data/project
// @response ProjectsResp
func queryProjectInfo(c *bm.Context) {
var (
req = &model.ProjectInfoRequest{}
err error
)
if err = c.Bind(req); err != nil {
return
}
if err = req.Verify(); err != nil {
c.JSON(nil, err)
return
}
c.JSON(srv.QueryProjectInfo(c, req))
}

View File

@@ -0,0 +1,21 @@
package http
import (
"go-common/app/admin/ep/saga/model"
bm "go-common/library/net/http/blademaster"
)
func queryProjectRunners(c *bm.Context) {
var (
req = &model.ProjectDataReq{}
err error
)
if err = c.Bind(req); err != nil {
return
}
if req.Username, err = getUsername(c); err != nil {
c.JSON(nil, err)
return
}
c.JSON(srv.QueryProjectRunners(c, req))
}

View File

@@ -0,0 +1,20 @@
package http
import (
"go-common/app/admin/ep/saga/model"
bm "go-common/library/net/http/blademaster"
)
// @params TasksReq
// @router get /ep/admin/saga/v1/tasks/project
// @response TasksResp
func projectTasks(ctx *bm.Context) {
var (
req = &model.TasksReq{}
err error
)
if err = ctx.Bind(req); err != nil {
return
}
ctx.JSON(srv.MergeTasks(ctx, req))
}

View File

@@ -0,0 +1,23 @@
package http
import (
"go-common/app/admin/ep/saga/conf"
"go-common/app/admin/ep/saga/model"
bm "go-common/library/net/http/blademaster"
)
// @params Empty
// @router get /ep/admin/saga/v1/data/teams
// @response TeamInfoResp
func queryTeams(c *bm.Context) {
if _, err := getUsername(c); err != nil {
c.JSON(nil, err)
return
}
resp := &model.TeamInfoResp{
Department: conf.Conf.Property.DeInfo,
Business: conf.Conf.Property.BuInfo,
}
c.JSON(resp, nil)
}

View File

@@ -0,0 +1,34 @@
package http
import (
"go-common/library/ecode"
bm "go-common/library/net/http/blademaster"
)
const (
_sessUnKey = "username"
)
// @params EmptyReq
// @router get /ep/admin/saga/v1/user
// @response User
func queryUserInfo(ctx *bm.Context) {
var (
userName string
err error
)
if userName, err = getUsername(ctx); err != nil {
return
}
ctx.JSON(srv.UserInfo(userName), nil)
}
func getUsername(c *bm.Context) (string, error) {
if user, err := c.Request.Cookie(_sessUnKey); err == nil {
return user.Value, nil
}
if value, exist := c.Get(_sessUnKey); exist {
return value.(string), nil
}
return "", ecode.AccessKeyErr
}

View File

@@ -0,0 +1,126 @@
package http
import (
"go-common/app/admin/ep/saga/model"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/http/blademaster/binding"
)
//queryContactLogs queryContactLogs
func queryContactLogs(c *bm.Context) {
v := &model.QueryContactLogRequest{}
if err := c.Bind(v); err != nil {
return
}
if err := v.Verify(); err != nil {
c.JSON(nil, err)
return
}
c.JSON(srv.QueryContactLogs(c, v))
}
//queryContactLogs queryContactLogs
func queryRedisdata(c *bm.Context) {
v := &model.QueryContactLogRequest{}
if err := c.Bind(v); err != nil {
return
}
if err := v.Verify(); err != nil {
c.JSON(nil, err)
return
}
c.JSON(srv.QueryContactLogs(c, v))
}
// SyncContacts sync the wechat contacts 更新企业微信列表用户信息和saga信息
func queryContacts(ctx *bm.Context) {
var (
req = &model.EmptyReq{}
v = &model.Pagination{}
err error
)
if err = ctx.Bind(req); err != nil {
ctx.JSON(nil, err)
return
}
if err = ctx.Bind(v); err != nil {
return
}
ctx.JSON(srv.QueryContacts(ctx, v))
}
func createWechat(ctx *bm.Context) {
var (
username string
err error
)
req := &model.CreateChatReq{}
if err = ctx.BindWith(req, binding.JSON); err != nil {
ctx.JSON(nil, err)
return
}
if username, err = getUsername(ctx); err != nil {
ctx.JSON(nil, err)
return
}
ctx.JSON(srv.CreateWechat(ctx, req, username))
}
func queryWechatCreateLog(ctx *bm.Context) {
var (
req = &model.Pagination{}
err error
user string
)
if err = ctx.Bind(req); err != nil {
return
}
if err = req.Verify(); err != nil {
ctx.JSON(nil, err)
return
}
if user, err = getUsername(ctx); err != nil {
ctx.JSON(nil, err)
return
}
ctx.JSON(srv.QueryWechatCreateLog(ctx, req, user))
}
func getWechat(ctx *bm.Context) {
ctx.JSON(srv.WechatParams(ctx, ctx.Request.Form.Get("chatid")))
}
func sendGroupWechat(ctx *bm.Context) {
req := &model.SendChatReq{}
if err := ctx.BindWith(req, binding.JSON); err != nil {
ctx.JSON(nil, err)
return
}
ctx.JSON(srv.SendGroupWechat(ctx, req))
}
func sendWechat(ctx *bm.Context) {
req := &model.SendMessageReq{}
if err := ctx.BindWith(req, binding.JSON); err != nil {
ctx.JSON(nil, err)
return
}
ctx.JSON(srv.SendWechat(ctx, req))
}
func updateWechat(ctx *bm.Context) {
req := &model.UpdateChatReq{}
if err := ctx.BindWith(req, binding.JSON); err != nil {
ctx.JSON(nil, err)
return
}
ctx.JSON(srv.UpdateWechat(ctx, req))
}
func syncWechatContacts(ctx *bm.Context) {
ctx.JSON(srv.SyncWechatContacts(ctx))
}

View File

@@ -0,0 +1,45 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"basic.go",
"branch.go",
"config.go",
"job.go",
"mail.go",
"model.go",
"mr.go",
"project.go",
"statistics.go",
"sync.go",
"team.go",
"wechat.go",
],
importpath = "go-common/app/admin/ep/saga/model",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/ecode:go_default_library",
"//vendor/github.com/xanzy/go-gitlab: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,7 @@
package model
// QueryTypeItem ...
type QueryTypeItem struct {
Name interface{} `json:"name" validate:"required"`
Value interface{} `json:"value"`
}

View File

@@ -0,0 +1,61 @@
package model
import "time"
// BranchDeleted ...
const BranchDeleted = true
// BranchDiffWithRequest ...
type BranchDiffWithRequest struct {
ProjectID int `form:"project_id"`
Master string `form:"comparator"`
SortBy string `form:"sort_by"`
Branch string `form:"branch"`
Username string `form:"username"`
}
// BranchDiffWithResponse ...
type BranchDiffWithResponse struct {
Branch string `json:"branch"`
Behind int `json:"behind"`
Ahead int `json:"ahead"`
LatestSyncTime *time.Time `json:"latest_sync_time"`
LatestUpdateTime *time.Time `json:"latest_update_time"`
}
// CommitTreeNode ...
type CommitTreeNode struct {
CommitID string `json:"commit_id"`
Parents []string `json:"parents"`
CreatedAt *time.Time `json:"created_at"`
Author string `json:"author"`
}
// StatisticsBranches ...
type StatisticsBranches struct {
ID int `json:"id" gorm:"AUTO_INCREMENT;primary_key;" form:"id"`
ProjectID int `json:"project_id"`
ProjectName string `json:"project_name"`
CommitID string `json:"commit_id"`
BranchName string `json:"branch_name"`
Protected bool `json:"protected"`
Merged bool `json:"merged"`
DevelopersCanPush bool `json:"developers_can_push"`
DevelopersCanMerge bool `json:"developers_can_merge"`
IsDeleted bool `json:"is_deleted"`
}
// AggregateBranches ...
type AggregateBranches struct {
ID int `json:"id" gorm:"AUTO_INCREMENT;primary_key;" form:"id"`
ProjectID int `json:"project_id"`
ProjectName string `json:"project_name"`
BranchName string `json:"branch_name"`
BranchUserName string `json:"branch_user_name"`
BranchMaster string `json:"branch_master"`
Behind int `json:"behind"`
Ahead int `json:"ahead"`
LatestSyncTime *time.Time `json:"latest_sync_time"`
LatestUpdateTime *time.Time `json:"latest_update_time"`
IsDeleted bool `json:"is_deleted"`
}

View File

@@ -0,0 +1,166 @@
package model
import "time"
// CommonResp ...
type CommonResp struct {
Code int `json:"code"`
Message string `json:"message"`
Ttl int `json:"ttl"`
}
// BuildNewFile ...
type BuildNewFile struct {
ID int `json:"id"`
AppID int `json:"app_id"`
Name string `json:"name"`
Comment string `json:"comment"`
From int `json:"from"`
State int `json:"state"`
Mark string `json:"mark"`
Operator string `json:"operator"`
IsDelete int `json:"is_delete"`
NewCommon int `json:"new_common"`
Ctime int `json:"ctime"`
Mtime int `json:"mtime"`
}
// BuildFile ...
type BuildFile struct {
*BuildNewFile
LastConf *BuildNewFile `json:"last_conf"`
}
// ConfigData ...
type ConfigData struct {
Files []*BuildFile `json:"files"`
BuildFiles []*BuildFile `json:"build_files"`
BuildNewFile []*BuildNewFile `json:"build_new_file"`
}
// ConfigsParam ...
type ConfigsParam struct {
AppName string
TreeID int
Env string
Zone string
BuildId int
Build string
Token string
FilenameGo string
FilenameRunnerJava string
FilenameTokenJava string
FilenameRunnerCommon string
Increment int
Force int
AutoRequiredParams []string
RequiredParams []string
Comment *ConfigComment
}
// SagaConfigsParam ...
type SagaConfigsParam struct {
FileName string
AppName string
TreeID int
Env string
Zone string
BuildId int
Build string
Token string
Increment int
Force int
UserList []string
}
// ConfigComment ...
type ConfigComment struct {
CommentURL string
}
// TagUpdate ...
type TagUpdate struct {
Mark string `form:"mark"`
Names string `form:"names"`
}
// SvenResp ...
type SvenResp struct {
CommonResp
Data *ConfigData `json:"data"`
}
// ConfigValueResp ...
type ConfigValueResp struct {
CommonResp
Data *BuildNewFile `json:"data"`
}
// Config ...
type Config struct {
Property *Property
}
// Property ...
type Property struct {
Repos []*RepoConfig
}
// ConfigList ...
type ConfigList struct {
ProjectID int `json:"project_id" validate:"required"`
Configs []ConfigSagaItem `json:"configs"`
}
// ConfigSagaItem ...
type ConfigSagaItem struct {
Name string `json:"name" validate:"required"`
Value interface{} `json:"value"`
}
// SagaConfigLogResp ...
type SagaConfigLogResp struct {
Id int `form:"id" gorm:"column:id"`
Username string `form:"username" json:"username" gorm:"column:username"`
ProjectId int `form:"project_id" json:"project_id" gorm:"column:project_id"`
Content string `form:"content" json:"content" gorm:"column:content"`
Ctime time.Time `form:"ctime" json:"ctime" gorm:"column:ctime"`
Mtime time.Time `form:"mtime" json:"mtime" gorm:"column:mtime"`
UpdateUser string `form:"update_user" json:"update_user" gorm:"column:update_user"`
Status int `form:"status" json:"status" gorm:"column:status"` //1创建 2修改 3同步中 4同步完成 5同步失败
}
// UpdateConfigReq ...
type UpdateConfigReq struct {
Ids []int `json:"ids" validate:"required"`
ConfigID string `json:"config_id" validate:"required"`
ConfigName string `json:"config_name" validate:"required"`
Mark string `json:"mark" validate:"required"`
}
// OptionSagaItem ...
type OptionSagaItem struct {
ConfigSagaItem
CNName string `json:"cn_name"`
Remark string `json:"remark"`
Type string `json:"type"`
Require bool `json:"require"`
}
// RepoConfig ...
type RepoConfig struct {
URL string
Group string
Name string
GName string // gitlab仓库别名
Language string
AuthBranches []string // 鉴权分支
TargetBranches []string // 分支白名单
LockTimeout int32
MinReviewer int
RelatePipeline bool
DelayMerge bool
LimitAuth bool
AllowLabel string
SuperAuthUsers []string
}

View File

@@ -0,0 +1,44 @@
package model
import "time"
// ProjectJobRequest ...
type ProjectJobRequest struct {
ProjectID int `form:"project_id"`
Scope string `form:"state"`
User string `form:"user"`
Branch string `form:"branch"`
Machine string `form:"machine"`
StatisticsType int `form:"statistics_type"`
Username string `form:"username"`
}
// ProjectJobResp ...
type ProjectJobResp struct {
ProjectID int `json:"project_id"`
QueryDescription string `json:"query_description"`
TotalItem int `json:"total"`
State string `json:"state"`
DataInfo []*DateJobInfo `json:"data_info"`
}
// DateJobInfo ...
type DateJobInfo struct {
Date string `json:"date"`
JobTotal int `json:"total_num"`
StatusNum int `json:"status_num"`
PendingTime float64 `json:"pending_time"`
RunningTime float64 `json:"running_time"`
SlowestPendingJob []*ProjectJob `json:"slowest_pending_jobs"`
}
// ProjectJob ...
type ProjectJob struct {
Status string
User string
Branch string
Machine string
CreatedAt *time.Time
StartedAt *time.Time
FinishedAt *time.Time
}

View File

@@ -0,0 +1,27 @@
package model
// Mail def.
type Mail struct {
ToAddress []*MailAddress
Subject string
Body string
}
// MailAddress def.
type MailAddress struct {
Address string
Name string
}
// MailData def.
type MailData struct {
UserName string
SourceBranch string
TargetBranch string
Title string
Description string
URL string
Info string
PipelineStatus string
PipeStatus string
}

View File

@@ -0,0 +1,84 @@
package model
// pagination.
const (
DefaultPageSize = 10
DefaultPageNum = 1
)
// Pagination Pagination.
type Pagination struct {
PageSize int `form:"page_size"`
PageNum int `form:"page_num"`
}
// TeamParam struct for organization Info.
type TeamParam struct {
Department string `form:"department"`
Business string `form:"business"`
}
// EmptyReq params for request without params
type EmptyReq struct {
}
// EmptyResp resp for response without data
type EmptyResp struct {
}
// Task def
type Task struct {
ID int64 `json:"id,omitempty" gorm:"column:id"`
ProjID int `json:"proj_id,omitempty" gorm:"column:proj_id"`
EventType string `json:"event_type,omitempty" gorm:"column:event_type"`
Author string `json:"author,omitempty" gorm:"author"`
MRID int `json:"mr_id,omitempty" gorm:"column:mr_id"`
URL string `json:"url,omitempty" gorm:"column:url"`
Status int `json:"status,omitempty" gorm:"status"`
TaskDetails string `json:"task_details,omitempty" gorm:"task_details"`
SourceBranch string `json:"source_branch,omitempty" gorm:"source_branch"`
TargetBranch string `json:"target_branch,omitempty" gorm:"target_branch"`
Title string `json:"title,omitempty" gorm:"title"`
}
// TasksReq params for tasks
type TasksReq struct {
ProjID int `form:"proj_id" validate:"required"`
Statuses []int `form:"statuses,split" default:"3,4"` // 3 - running, 4 - waiting, 默认查运行中和等待的任务
}
// TasksResp resp for tasks
type TasksResp struct {
Tasks []*Task `json:"tasks,omitempty"`
}
// User User.
type User struct {
Name string `json:"username" gorm:"column:name"`
EMail string `json:"email" gorm:"column:email"`
}
// RequireVisibleUser def
type RequireVisibleUser struct {
UserName string
NickName string
}
// ContactInfo def
type ContactInfo struct {
ID string `json:"id,omitempty" gorm:"column:id"`
UserName string `json:"english_name" gorm:"column:user_name"`
UserID string `json:"userid" gorm:"column:user_id"`
NickName string `json:"name" gorm:"column:nick_name"`
VisibleSaga bool `json:"visible_saga" gorm:"column:visible_saga"`
}
// AlmostEqual return the compare result with fields
func (contact *ContactInfo) AlmostEqual(other *ContactInfo) bool {
if contact.UserID == other.UserID &&
contact.UserName == other.UserName &&
contact.NickName == other.NickName {
return true
}
return false
}

View File

@@ -0,0 +1,51 @@
package model
import "time"
// ProjectMrReportReq ...
type ProjectMrReportReq struct {
ProjectID int `form:"project_id"`
Member string `form:"member"`
Username string `form:"username"`
}
// ProjectMrReportResp ...
type ProjectMrReportResp struct {
ChangeAdd int `json:"change_add"`
ChangeDel int `json:"change_del"`
MrCount int `json:"mr_count"`
StateCount int `json:"merged_count"`
Discussion int `json:"discussion"`
Resolve int `json:"resolved_discussion"`
AverageMerge string `json:"average_merge_time"`
Reviewers []string `json:"reviewer"`
SpentTime string `json:"spent_time"`
ReviewerOther []string `json:"reviewer_other"`
ReviewChangeAdd int `json:"review_add"`
ReviewChangeDel int `json:"review_del"`
ReviewTotalTime string `json:"review_total_time"`
}
// MrReviewer ...
type MrReviewer struct {
ID int `json:"id"`
Name string `json:"name"`
FinishedAt *time.Time `json:"finished_at"`
UserType string `json:"type"`
// SpentTime 其实是反应时间+review时间
SpentTime int `json:"spent_time"`
}
// MrInfo ...
type MrInfo struct {
ProjectID int `json:"project_id"`
MrID int `json:"mr_id"`
State string `json:"state"`
SpentTime int `json:"spent_time"`
Author string `json:"author"`
ChangeAdd int `json:"change_add"`
ChangeDel int `json:"change_del"`
TotalDiscussion int `json:"total_discussion"`
SolvedDiscussion int `json:"solved_discussion"`
Reviewers []*MrReviewer `json:"reviewers"`
}

View File

@@ -0,0 +1,87 @@
package model
import "go-common/library/ecode"
// ProjectInfo def
type ProjectInfo struct {
ProjectID int `json:"project_id" gorm:"column:project_id"`
Name string `json:"name" gorm:"column:name"`
Description string `json:"description" gorm:"column:description"`
WebURL string `json:"web_url" gorm:"column:web_url"`
Repo string `json:"repo" gorm:"column:repo"`
DefaultBranch string `json:"default_branch" gorm:"column:default_branch"`
Owner string `json:"owner" gorm:"column:owner"`
SpaceName string `json:"namespace_name" gorm:"column:namespace_name"`
SpaceKind string `json:"namespace_kind" gorm:"column:namespace_kind"`
Saga bool `json:"saga" gorm:"column:saga"`
Runner bool `json:"runner" gorm:"column:runner"`
Department string `json:"department" gorm:"column:department"`
Business string `json:"business" gorm:"column:business"`
Language string `json:"language" gorm:"column:language"`
}
// ProjectInfoRequest Project Info Request.
type ProjectInfoRequest struct {
Pagination
TeamParam
Username string `form:"username"`
Name string `form:"name"`
}
// ProjectInfoResp ...
type ProjectInfoResp struct {
Total int `json:"total"`
Saga int `json:"saga"`
Runner int `json:"runner"`
SagaScale int `json:"saga_scale"`
RunnerScale int `json:"runner_scale"`
PageNum int `json:"page_num"`
PageSize int `json:"page_size"`
ProjectInfo []*MyProjectInfo `json:"ProjectInfo"`
}
// FavoriteProjectsResp resp for favorite projects
type FavoriteProjectsResp struct {
Total int `json:"total"`
Pagination
Projects []*MyProjectInfo `json:"projects,omitempty"`
}
// ProjectFavorite def
type ProjectFavorite struct {
ID int64 `json:"id,omitempty" gorm:"column:id"`
UserName string `json:"user_name,omitempty" gorm:"column:user_name"`
ProjID int `json:"proj_id,omitempty" gorm:"column:proj_id"`
}
// EditFavoriteReq params for edit favorite
type EditFavoriteReq struct {
ProjID int `json:"proj_id" validate:"required"`
Star bool `json:"star"`
}
// MyProjectInfo mask as star
type MyProjectInfo struct {
*ProjectInfo
Star bool `json:"is_star"`
}
// ProjectsInfoResp resp for query projects with start mark
type ProjectsInfoResp struct {
Projects []*MyProjectInfo `json:"projects"`
}
// Verify verify the value of pageNum and pageSize.
func (p *Pagination) Verify() error {
if p.PageNum < 0 {
return ecode.MerlinIllegalPageNumErr
} else if p.PageNum == 0 {
p.PageNum = DefaultPageNum
}
if p.PageSize < 0 {
return ecode.MerlinIllegalPageSizeErr
} else if p.PageSize == 0 {
p.PageSize = DefaultPageSize
}
return nil
}

View File

@@ -0,0 +1,195 @@
package model
// ExpiredOneDay ...
const ExpiredOneDay = 86400
// Status ...
const (
StatusCancel = "cancel"
StatusMerged = "merged"
StatusClosed = "closed"
)
// number per year.
const (
MonthNumPerYear = 12
DayNumPerYear = 365
DayNumPerWeek = 7
DayNumPerMonth = 30
)
// query type.
const (
LastYearPerMonth = iota
LastMonthPerDay
LastYearPerDay
LastWeekPerDay
)
// query type note.
const (
LastYearPerMonthNote = "最近一年每月数量"
LastMonthPerDayNote = "上一月每天数量"
LastYearPerDayNote = "最近一年每天数量"
)
// query object type.
const (
ObjectMR = "mr"
ObjectCommit = "commit"
ObjectSaga = "saga"
ObjectRunner = "runner"
)
// KeyTypeConst ...
var KeyTypeConst = map[int]string{
0: "LastYearPerMonth",
1: "LastMonthPerDay",
2: "LastYearPerDay",
3: "LastWeekPerDay",
}
// CommitRequest ...
type CommitRequest struct {
TeamParam
Since string `form:"since"`
Until string `form:"until"`
Username string `form:"username"`
}
// ProjectCommit ...
type ProjectCommit struct {
ProjectID int `json:"project_id"`
Name string `json:"name"`
CommitNum int `json:"commit_num"`
}
// CommitResp ...
type CommitResp struct {
Total int `json:"total"`
ProjectCommit []*ProjectCommit `json:"commit_per_project"`
}
// ProjectDataReq ...
type ProjectDataReq struct {
ProjectID int `form:"project_id" validate:"required"`
ProjectName string `form:"project_name"`
QueryType int `form:"query_type"`
Username string `form:"username"`
}
// ProjectDataResp ...
type ProjectDataResp struct {
ProjectName string `json:"project_name"`
QueryDes string `json:"query_description"`
Total int `json:"total"`
Data []*DataWithTime `json:"data_info"`
}
// TeamDataRequest ...
type TeamDataRequest struct {
TeamParam
QueryType int `form:"query_type"`
Username string `form:"username"`
}
// TeamDataResp ...
type TeamDataResp struct {
Department string `json:"department"`
Business string `json:"business"`
QueryDes string `json:"query_description"`
Total int `json:"total"`
Data []*DataWithTime `json:"data_info"`
}
// DataWithTime ...
type DataWithTime struct {
TotalItem int `json:"total_item"`
StartTime string `json:"start_time"`
EndTime string `json:"end_time"`
}
// PipelineDataTime ...
type PipelineDataTime struct {
TotalItem int `json:"total_item"`
SuccessItem int `json:"success_item"`
StartTime string `json:"start_time"`
EndTime string `json:"end_time"`
}
// PipelineDataResp ...
type PipelineDataResp struct {
Department string `json:"department"`
Business string `json:"business"`
QueryDes string `json:"query_description"`
Total int `json:"total"`
SuccessNum int `json:"success_num"`
SuccessScale int `json:"success_scale"`
Data []*PipelineDataTime `json:"data_info"`
}
// PipelineDataReq ...
type PipelineDataReq struct {
ProjectID int `form:"project_id" validate:"required"`
ProjectName string `form:"project_name"`
Branch string `form:"branch"`
State string `form:"state"`
User string `form:"user"`
Type int `form:"query_type"` //0 最近一年每月数量;1 上一月每天数量;2 最近一年每天数量
StatisticsType int `form:"statistics_type"`
Username string `form:"username"`
}
// PipelineDataAvgResp ...
type PipelineDataAvgResp struct {
ProjectName string `json:"project_name"`
QueryDes string `json:"query_description"`
Status string `json:"status"`
Total int `json:"total"`
TotalStatus int `json:"total_status"`
AvgDurationTime float64 `json:"avg_duration_time"`
AvgPendingTime float64 `json:"avg_pending_time"`
AvgRunningTime float64 `json:"avg_running_time"`
Data []*PipelineDataAvg `json:"data_info"`
}
// PipelineDataAvg ...
type PipelineDataAvg struct {
TotalItem int `json:"total_item"`
TotalStatusItem int `json:"total_status_item"`
AvgDurationTime float64 `json:"avg_total_time"`
MaxDurationTime float64 `json:"max_duration_time"`
MinDurationTime float64 `json:"min_duration_time"`
AvgPendingTime float64 `json:"avg_pending_time"`
MaxPendingTime float64 `json:"max_pending_time"`
MinPendingTime float64 `json:"min_pending_time"`
AvgRunningTime float64 `json:"avg_running_time"`
MaxRunningTime float64 `json:"max_running_time"`
MinRunningTime float64 `json:"min_running_time"`
StartTime string `json:"start_time"`
EndTime string `json:"end_time"`
}
// PipelineTime ...
type PipelineTime struct {
PendingMax float64
PendingMin float64
RunningMax float64
RunningMin float64
DurationMax float64
DurationMin float64
PendingList []float64
RunningList []float64
DurationList []float64
}
// AlertPipeline ...
type AlertPipeline struct {
ProjectName string
ProjectID int
RunningTimeout int
RunningRate int
RunningThreshold int
PendingTimeout int
PendingThreshold int
}

View File

@@ -0,0 +1,293 @@
package model
import (
"time"
"github.com/xanzy/go-gitlab"
)
// DatabaseErrorText ...
const (
DatabaseErrorText = "Incorrect string value"
DatabaseMaxLenthErrorText = "Data too long for column"
MessageMaxLen = 2048
JsonMarshalErrorText = "XXXXX"
)
// DataType ...
const (
DataTypePipeline = "pipeline"
DataTypeJob = "job"
DataTypeCommit = "commit"
DataTypeMR = "MR"
DataTypeBranch = "Branch"
)
// FailData ...
type FailData struct {
ChildID int
ChildIDStr string
SunID int
}
// SyncResult ...
type SyncResult struct {
TotalPage int
TotalNum int
FailData []*FailData
}
// StatisticsCommits ...
type StatisticsCommits struct {
ID int `json:"id" gorm:"AUTO_INCREMENT;primary_key;" form:"id"`
CommitID string `json:"commit_id"`
ProjectID int `json:"project_id" gorm:"column:project_id"`
ProjectName string `json:"project_name"`
ShortID string `json:"short_id"`
Title string `json:"title"`
AuthorName string `json:"author_name"`
AuthoredDate *time.Time `json:"authored_date"`
CommitterName string `json:"committer_name"`
CommittedDate *time.Time `json:"committed_date"`
CreatedAt *time.Time `json:"created_at"`
Message string `json:"message"`
ParentIDs string `json:"parent_ids"`
StatsAdditions int `json:"stats_additions"`
StatsDeletions int `json:"stats_deletions"`
Status string `json:"status" default:""`
}
// StatisticsIssues ...
type StatisticsIssues struct {
ID int `json:"id" gorm:"AUTO_INCREMENT;primary_key;" form:"id"`
ProjectID int `json:"project_id"`
IssueID int `json:"issue_id"`
IssueIID int `json:"issue_iid" gorm:"column:issue_iid"`
MilestoneID int `json:"milestone_id"`
AuthorID int `json:"author_id"`
AuthorName string `json:"author_name"`
Description string `json:"description"`
State string `json:"state"`
Assignees string `json:"assignees"`
AssigneeID int `json:"assignee_id"`
AssigneeName string `json:"assignee_name"`
Upvotes int `json:"upvotes"`
Downvotes int `json:"downvotes"`
Labels string `json:"labels"`
Title string `json:"title"`
UpdatedAt *time.Time `json:"updated_at"`
CreatedAt *time.Time `json:"created_at"`
ClosedAt *time.Time `json:"closed_at"`
Subscribed bool `json:"subscribed"`
UserNotesCount int `json:"user_notes_count"`
DueDate *gitlab.ISOTime `json:"due_date"`
WebURL string `json:"web_url"`
TimeStats string `json:"time_stats"`
Confidential bool `json:"confidential"`
Weight int `json:"weight"`
DiscussionLocked bool `json:"discussion_locked"`
IssueLinkID int `json:"issue_link_id"`
}
// StatisticsRunners ...
type StatisticsRunners struct {
ID int `json:"id" gorm:"AUTO_INCREMENT;primary_key;" form:"id"`
ProjectID int `json:"project_id"`
ProjectName string `json:"project_name"`
RunnerID int `json:"runner_id"`
Description string `json:"description"`
Active bool `json:"active"`
IsShared bool `json:"is_shared"`
IPAddress string `json:"ip_address"`
Name string `json:"name"`
Online bool `json:"online"`
Status string `json:"status"`
Token string `json:"token"`
}
// StatisticsJobs ...
type StatisticsJobs struct {
ID int `json:"id" gorm:"AUTO_INCREMENT;primary_key;" form:"id"`
ProjectID int `json:"project_id"`
ProjectName string `json:"project_name"`
CommitID string `json:"commit_id"`
CreatedAt *time.Time `json:"created_at"`
Coverage float64 `json:"coverage"`
ArtifactsFile string `json:"artifacts_file"`
FinishedAt *time.Time `json:"finished_at"`
JobID int `json:"job_id"`
Name string `json:"name"`
Ref string `json:"ref"`
RunnerID int `json:"runner_id"`
RunnerDescription string `json:"runner_description"`
Stage string `json:"stage"`
StartedAt *time.Time `json:"started_at"`
Status string `json:"status"`
Tag bool `json:"tag"`
UserID int `json:"user_id"`
UserName string `json:"user_name"`
WebURL string `json:"web_url"`
}
// StatisticsMrs ...
type StatisticsMrs struct {
ID int `json:"id" gorm:"AUTO_INCREMENT;primary_key;" form:"id"`
MRID int `json:"mr_id"`
MRIID int `json:"mr_iid" gorm:"column:mr_iid"`
TargetBranch string `json:"target_branch"`
SourceBranch string `json:"source_branch"`
ProjectID int `json:"project_id"`
ProjectName string `json:"project_name"`
Title string `json:"title"`
State string `json:"state"`
CreatedAt *time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at"`
Upvotes int `json:"upvotes"`
Downvotes int `json:"downvotes"`
AuthorID int `json:"author_id"`
AuthorName string `json:"author_name"`
AssigneeID int `json:"assignee_id"`
AssigneeName string `json:"assignee_name"`
SourceProjectID int `json:"source_project_id"`
TargetProjectID int `json:"target_project_id"`
Labels string `json:"labels"`
Description string `json:"description"`
WorkInProgress bool `json:"work_in_progress"`
MilestoneID int `json:"milestone_id"`
MergeWhenPipelineSucceeds bool `json:"merge_when_pipeline_succeeds"`
MergeStatus string `json:"merge_status"`
MergedByID int `json:"merged_by_id"`
MergedByName string `json:"merged_by_name"`
MergedAt *time.Time `json:"merged_at"`
ClosedByID int `json:"closed_by_id"`
ClosedAt *time.Time `json:"closed_at"`
Subscribed bool `json:"subscribed"`
SHA string `json:"sha"`
MergeCommitSHA string `json:"merge_commit_sha"`
UserNotesCount int `json:"user_notes_count"`
ChangesCount string `json:"changes_count"`
ShouldRemoveSourceBranch bool `json:"should_remove_source_branch"`
ForceRemoveSourceBranch bool `json:"force_remove_source_branch"`
WebURL string `json:"web_url"`
DiscussionLocked bool `json:"discussion_locked"`
Changes string `json:"changes"`
TimeStatsHumanTimeEstimate string `json:"time_stats_human_time_estimate"`
TimeStatsHumanTotalTimeSpent string `json:"time_stats_human_total_time_spent"`
TimeStatsTimeEstimate int `json:"time_stats_time_estimate"`
TimeStatsTotalTimeSpent int `json:"time_stats_total_time_spent"`
Squash bool `json:"squash"`
PipelineID int `json:"pipeline_id"`
ChangeAdd int `json:"change_add"`
ChangeDel int `json:"change_del"`
TotalDiscussion int `json:"total_discussion"`
SolvedDiscussion int `json:"solved_discussion"`
}
// AggregateMrReviewer ...
type AggregateMrReviewer struct {
ID int `json:"id" gorm:"AUTO_INCREMENT;primary_key;" form:"id"`
ProjectID int `json:"project_id"`
ProjectName string `json:"project_name"`
MrIID int `json:"mr_iid" gorm:"column:mr_iid"`
Title string `json:"title"`
WebUrl string `json:"web_url"`
AuthorName string `json:"author_name"`
ReviewerID int `json:"reviewer_id"`
ReviewerName string `json:"reviewer_name"`
ReviewType string `json:"review_type"`
ReviewID int `json:"review_id"`
ReviewCommand string `json:"review_command"`
CreatedAt *time.Time `json:"created_at"`
UserType string `json:"type"`
ApproveTime int `json:"approve_time"` // SpentTime 其实是反应时间+review时间
MergeTime int `json:"merge_time"`
}
// StatisticsPipeline ...
type StatisticsPipeline struct {
ID int `json:"id" gorm:"AUTO_INCREMENT;primary_key;" form:"id"`
PipelineID int `json:"pipeline_id" gorm:"column:pipeline_id"`
ProjectName string `json:"project_name"`
ProjectID int `json:"project_id" gorm:"column:project_id"`
Status string `json:"status" gorm:"column:status" default:""`
Ref string `json:"ref" gorm:"column:ref"`
Tag bool `json:"tag" gorm:"column:tag"`
User string `json:"user" gorm:"column:user"`
UpdatedAt *time.Time `json:"updated_at" gorm:"column:updated_at"`
CreatedAt *time.Time `json:"created_at" gorm:"column:created_at"`
StartedAt *time.Time `json:"started_at" gorm:"column:started_at"`
FinishedAt *time.Time `json:"finished_at" gorm:"column:finished_at"`
CommittedAt *time.Time `json:"committed_at" gorm:"column:committed_at"`
Duration int `json:"duration" gorm:"column:duration"`
Coverage string `json:"coverage" gorm:"column:coverage"`
DurationTime int `json:"duration_time"`
}
// StatisticsNotes ...
type StatisticsNotes struct {
ID int `json:"id" gorm:"AUTO_INCREMENT;primary_key;" form:"id"`
ProjectID int `json:"project_id"`
ProjectName string `json:"project_name"`
MrIID int `json:"mr_iid" gorm:"column:mr_iid"`
IssueIID int `json:"issue_iid" gorm:"column:issue_iid"`
NoteID int `json:"note_id"`
Body string `json:"body"`
Attachment string `json:"attachment"`
Title string `json:"title"`
FileName string `json:"file_name"`
AuthorID int `json:"author_id"`
AuthorName string `json:"author_name"`
System bool `json:"system"`
ExpiresAt *time.Time `json:"expires_at"`
UpdatedAt *time.Time `json:"updated_at"`
CreatedAt *time.Time `json:"created_at"`
NoteableID int `json:"noteable_id"`
NoteableType string `json:"noteable_type"`
Position string `json:"position"`
Resolvable bool `json:"resolvable"`
Resolved bool `json:"resolved"`
ResolvedByID int `json:"resolved_by_id"`
ResolvedByName string `json:"resolved_by_name"`
NoteableIID int `json:"noteable_iid" gorm:"column:noteable_iid"`
}
// StatisticsMembers ...
type StatisticsMembers struct {
ID int `json:"id" gorm:"AUTO_INCREMENT;primary_key;" form:"id"`
ProjectID int `json:"project_id"`
ProjectName string `json:"project_name"`
MemberID int `json:"member_id"`
Username string `json:"username"`
Email string `json:"email"`
Name string `json:"name"`
State string `json:"state"`
CreatedAt *time.Time `json:"created_at"`
AccessLevel int `json:"access_level"`
}
// StatisticsMRAwardEmojis ...
type StatisticsMRAwardEmojis struct {
ID int `json:"id" gorm:"AUTO_INCREMENT;primary_key;" form:"id"`
ProjectID int `json:"project_id"`
ProjectName string `json:"project_name"`
MrIID int `json:"mr_iid" gorm:"column:mr_iid"`
AwardEmojiID int `json:"award_emoji_id"`
Name string `json:"name"`
UserID int `json:"user_id"`
UserName string `json:"user_name"`
CreatedAt *time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at"`
AwardableID int `json:"awardable_id"`
AwardableType string `json:"awardable_type"`
}
// StatisticsDiscussions ...
type StatisticsDiscussions struct {
ID int `json:"id" gorm:"AUTO_INCREMENT;primary_key;" form:"id"`
ProjectID int `json:"project_id"`
ProjectName string `json:"project_name"`
MrIID int `json:"mr_iid" gorm:"column:mr_iid"`
DiscussionID string `json:"discussion_id"`
IndividualNote bool `json:"individual_note"`
Notes string `json:"notes"`
}

View File

@@ -0,0 +1,24 @@
package model
// PairKey def
type PairKey struct {
Label string `json:"label"`
Value string `json:"value"`
}
// TeamInfoResp def
type TeamInfoResp struct {
Department []*PairKey `json:"department"`
Business []*PairKey `json:"business"`
}
// Developer def
type Developer struct {
Department string `json:"department"`
Total int `json:"total"`
Android int `json:"android"`
Ios int `json:"ios"`
Web int `json:"web"`
Service int `json:"service"`
Other int `json:"other"`
}

View File

@@ -0,0 +1,191 @@
package model
import "time"
// MaxWechatLen ...
const MaxWechatLen = 254 //企业微信内容最大长度
// AppConfig def
type AppConfig struct {
AppID int // 企业微信SAGA应用的appId
AppSecret string // 企业微信SAGA应用的secret
}
// Notification def
type Notification struct {
ToUser string `json:"touser"`
ToParty string `json:"toparty"`
ToTag string `json:"totag"`
MsgType string `json:"msgtype"`
AgentID int `json:"agentid"`
}
// Text def
type Text struct {
Content string `json:"content"`
}
// TxtNotification 文本消息
type TxtNotification struct {
Notification
Body Text `json:"text"`
Safe int `json:"safe"`
}
// AllowUserInfo 应用可见名单列表
type AllowUserInfo struct {
Users []*UserInfo `json:"user"`
}
// UserInfo only contain userid now
type UserInfo struct {
UserID string `json:"userid"`
}
// QueryContactLogRequest Query Contact Log Request.
type QueryContactLogRequest struct {
Pagination
UserID int64 `form:"user_id"`
UserName string `form:"user_name"`
OperateUser string `form:"operate_user"`
OperateType string `form:"operate_type"`
}
// QueryContactRequest Query Contact Log Request.
type QueryContactRequest struct {
Pagination
}
// AboundContactLog Abound Contact Log.
type AboundContactLog struct {
ContactLog
Name string `json:"machine_name"`
}
// ContactLog Contact Log.
type ContactLog struct {
ID int64 `json:"-" gorm:"column:id"`
Username string `json:"username" gorm:"column:username"`
MachineID int64 `json:"machine_id" gorm:"column:machine_id"`
OperateType string `json:"operate_type" gorm:"column:operation_type"`
OperateResult string `json:"operate_result" gorm:"column:operation_result"`
OperateTime time.Time `json:"operate_time" gorm:"column:ctime;default:current_timestamp"`
UTime time.Time `json:"-" gorm:"column:mtime;default:current_timestamp on update current_timestamp"`
Type int `json:"type" gorm:"column:type"`
}
// Contact Contact info.
type Contact struct {
ID int64 `json:"-" gorm:"column:id"`
Username string `json:"user_name" gorm:"column:user_name"`
UserID string `json:"user_id" gorm:"column:user_id"`
}
// PaginateContactLog Paginate Contact Log.
type PaginateContactLog struct {
Total int64 `json:"total"`
PageNum int `json:"page_num"`
PageSize int `json:"page_size"`
MachineLogs []*AboundContactLog `json:"machine_logs"`
}
// PaginateContact Paginate Contact.
type PaginateContact struct {
Total int64 `json:"total"`
PageNum int `json:"page_num"`
PageSize int `json:"page_size"`
Contacts []*ContactInfo `json:"contacts"`
}
// CreateChatReq ...
type CreateChatReq struct {
Name string `json:"name" validate:"required"`
Owner string `json:"owner" validate:"required"`
UserList []string `json:"userlist" validate:"required"`
ChatID string `json:"chatid" validate:"required"`
}
// WechatCreateLog ...
type WechatCreateLog struct {
ID int `json:"id" gorm:"column:id"`
Name string `json:"name" gorm:"column:name"`
Owner string `json:"owner" gorm:"column:owner"`
ChatID string `json:"chatid" gorm:"column:chatid"`
Cuser string `json:"cuser" gorm:"column:cuser"`
Ctime time.Time `form:"ctime" json:"ctime" gorm:"column:ctime"`
Status int `form:"status" json:"status" gorm:"column:status"` //1创建 2修改 3同步中 4同步完成 5同步失败
}
// WechatChatLog ...
type WechatChatLog struct {
ID int `json:"id" gorm:"column:id"`
ChatID string `json:"chatid" gorm:"column:chatid"`
MsgType string `json:"msgtype" gorm:"column:msgtype"`
Content string `json:"content" gorm:"column:content"`
Safe int `json:"safe" gorm:"column:safe"`
Status int `form:"status" json:"status" gorm:"column:status"` //1成功 0失败
}
// WechatMessageLog ...
type WechatMessageLog struct {
ID int `json:"id" gorm:"column:id"`
Touser string `json:"touser" gorm:"column:touser"`
Content string `json:"content" gorm:"column:content"`
Status int `form:"status" json:"status" gorm:"column:status"` //1成功 0失败
}
// ChatResp ...
type ChatResp struct {
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
}
// CreateChatResp ...
type CreateChatResp struct {
*ChatResp
ChatID string `json:"chatid"`
}
// CreateChatLog ...
type CreateChatLog struct {
*WechatCreateLog
Buttons []string `json:"buttons"`
}
// CreateChatLogResp ...
type CreateChatLogResp struct {
Total int `json:"total"`
*Pagination
Logs []*CreateChatLog `json:"logs,omitempty"`
}
// GetChatResp ...
type GetChatResp struct {
*ChatResp
ChatInfo *CreateChatReq `json:"chat_info"`
}
// SendChatReq ...
type SendChatReq struct {
ChatID string `json:"chatid"`
MsgType string `json:"msgtype"`
Text struct {
Content string `json:"content"`
} `json:"text"`
Safe int `json:"safe"`
}
// SendMessageReq ...
type SendMessageReq struct {
Touser []string `json:"touser"`
Content string `json:"content"`
}
// UpdateChatReq ...
type UpdateChatReq struct {
ChatID string `json:"chatid"`
Name string `json:"name"`
Owner string `json:"owner"`
AddUserList []string `json:"add_user_list"`
DelUserList []string `json:"del_user_list"`
}

View File

@@ -0,0 +1,48 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["server_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/admin/ep/saga/api/grpc/v1:go_default_library",
"//library/net/rpc/warden:go_default_library",
"//library/net/rpc/warden/resolver:go_default_library",
"//library/net/rpc/warden/resolver/direct:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = ["server.go"],
importpath = "go-common/app/admin/ep/saga/server/grpc",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/admin/ep/saga/api/grpc/v1:go_default_library",
"//app/admin/ep/saga/service/wechat:go_default_library",
"//library/net/rpc/warden: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 grpc
import (
"context"
"go-common/app/admin/ep/saga/api/grpc/v1"
"go-common/app/admin/ep/saga/service/wechat"
"go-common/library/net/rpc/warden"
)
// New grpc server
func New(cfg *warden.ServerConfig, chat *wechat.Wechat) (*warden.Server, error) {
svr := warden.NewServer(cfg)
v1.RegisterSagaAdminServer(svr.Server(), &server{chat: chat})
return svr.Start()
}
var _ v1.SagaAdminServer = &server{}
type server struct {
chat *wechat.Wechat
}
func (s *server) PushMsg(ctx context.Context, req *v1.PushMsgReq) (*v1.PushMsgReply, error) {
err := s.chat.PushMsg(ctx, req.Username, req.Content)
return &v1.PushMsgReply{}, err
}

View File

@@ -0,0 +1,23 @@
package grpc
import (
"context"
"testing"
"go-common/app/admin/ep/saga/api/grpc/v1"
"go-common/library/net/rpc/warden"
"go-common/library/net/rpc/warden/resolver"
"go-common/library/net/rpc/warden/resolver/direct"
)
func TestGRPC(t *testing.T) {
resolver.Register(direct.New())
conn, err := warden.NewClient(nil).Dial(context.Background(), "direct://default/127.0.0.1:9000")
if err != nil {
t.Fatal(err)
}
client := v1.NewSagaAdminClient(conn)
if _, err = client.PushMsg(context.Background(), &v1.PushMsgReq{Username: []string{"wuwei"}, Content: "test"}); err != nil {
t.Error(err)
}
}

View File

@@ -0,0 +1,87 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"pipeline_test.go",
"service_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/admin/ep/saga/conf:go_default_library",
"//app/admin/ep/saga/model:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
"//vendor/github.com/xanzy/go-gitlab:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"basic.go",
"branch.go",
"collect_project.go",
"commit.go",
"config.go",
"data.go",
"job.go",
"member.go",
"mr.go",
"pipeline.go",
"project.go",
"runner.go",
"service.go",
"statistics.go",
"sync.go",
"task.go",
"user.go",
"wechat.go",
],
importpath = "go-common/app/admin/ep/saga/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/admin/ep/saga/conf:go_default_library",
"//app/admin/ep/saga/dao:go_default_library",
"//app/admin/ep/saga/model:go_default_library",
"//app/admin/ep/saga/service/gitlab:go_default_library",
"//app/admin/ep/saga/service/utils:go_default_library",
"//app/admin/ep/saga/service/wechat:go_default_library",
"//library/cache/memcache:go_default_library",
"//library/cache/redis:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//vendor/github.com/BurntSushi/toml:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/github.com/robfig/cron:go_default_library",
"//vendor/github.com/xanzy/go-gitlab:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/admin/ep/saga/service/gitlab:all-srcs",
"//app/admin/ep/saga/service/mail:all-srcs",
"//app/admin/ep/saga/service/utils:all-srcs",
"//app/admin/ep/saga/service/wechat:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,40 @@
package service
import (
"context"
"go-common/app/admin/ep/saga/conf"
"go-common/app/admin/ep/saga/model"
"go-common/library/log"
)
// QueryProjectStatus ...
func (s *Service) QueryProjectStatus(c context.Context, req *model.ProjectDataReq) (resp []string) {
return conf.Conf.Property.DefaultProject.Status
}
// QueryProjectTypes ...
func (s *Service) QueryProjectTypes(c context.Context, req *model.ProjectDataReq) (resp []*model.QueryTypeItem) {
queryTypes := conf.Conf.Property.DefaultProject.Types
for _, queryType := range queryTypes {
item := &model.QueryTypeItem{}
switch queryType {
case model.LastYearPerMonth:
item.Name = queryType
item.Value = model.LastYearPerMonthNote
case model.LastMonthPerDay:
item.Name = queryType
item.Value = model.LastMonthPerDayNote
case model.LastYearPerDay:
item.Name = queryType
item.Value = model.LastYearPerDayNote
default:
log.Warn("QueryProjectCommit Type is not in range")
return
}
resp = append(resp, item)
}
return
}

View File

@@ -0,0 +1,445 @@
package service
import (
"context"
"encoding/json"
"sort"
"go-common/app/admin/ep/saga/model"
"go-common/app/admin/ep/saga/service/utils"
"go-common/library/log"
"github.com/xanzy/go-gitlab"
)
// QueryProjectBranchList query project commit info according to project id.
func (s *Service) QueryProjectBranchList(c context.Context, req *model.ProjectDataReq) (resp []*gitlab.Branch, err error) {
var (
branch []*gitlab.Branch
response *gitlab.Response
branchItem []*gitlab.Branch
)
if branch, response, err = s.gitlab.ListProjectBranch(req.ProjectID, 1); err != nil {
return
}
page := 2
for page <= response.TotalPages {
if branchItem, _, err = s.gitlab.ListProjectBranch(req.ProjectID, page); err != nil {
return
}
branch = append(branch, branchItem...)
page++
}
resp = branch
return
}
// QueryBranchDiffWith ...
func (s *Service) QueryBranchDiffWith(c context.Context, req *model.BranchDiffWithRequest) (resp []*model.BranchDiffWithResponse, err error) {
var (
branchDiff []*model.BranchDiffWithResponse
)
// 默认主分支为master
if req.Master == "" {
req.Master = "master"
}
if branchDiff, err = s.AllBranchDiffWithMaster(c, req.ProjectID, req.Master); err != nil {
return
}
if req.Branch != "" {
for _, branch := range branchDiff {
if branch.Branch == req.Branch {
resp = append(resp, branch)
return
}
}
}
switch req.SortBy {
case "update":
sort.Slice(branchDiff, func(i, j int) bool {
if branchDiff[i].LatestUpdateTime.After(*branchDiff[j].LatestUpdateTime) {
return false
}
if branchDiff[i].LatestUpdateTime.Before(*branchDiff[j].LatestUpdateTime) {
return true
}
return true
})
case "sync":
sort.Slice(branchDiff, func(i, j int) bool {
if branchDiff[i].LatestSyncTime.After(*branchDiff[j].LatestSyncTime) {
return false
}
if branchDiff[i].LatestSyncTime.Before(*branchDiff[j].LatestSyncTime) {
return true
}
return true
})
case "ahead":
sort.Slice(branchDiff, func(i, j int) bool {
if branchDiff[i].Ahead > branchDiff[j].Ahead {
return true
}
if branchDiff[i].Ahead < branchDiff[j].Ahead {
return false
}
return true
})
case "behind":
sort.Slice(branchDiff, func(i, j int) bool {
if branchDiff[i].Behind > branchDiff[j].Behind {
return true
}
if branchDiff[i].Behind < branchDiff[j].Behind {
return false
}
return true
})
}
resp = branchDiff[:100]
return
}
// AllBranchDiffWithMaster ...
func (s *Service) AllBranchDiffWithMaster(c context.Context, project int, master string) (branchDiff []*model.BranchDiffWithResponse, err error) {
var (
branches map[string]*model.CommitTreeNode
tree []*model.CommitTreeNode
cacheKey string
)
log.Info("sync Branch diff start => %s", cacheKey)
if branches, err = s.AllProjectBranchInfo(c, project); err != nil {
return
}
if tree, err = s.BuildCommitTree(c, project); err != nil {
return
}
for k, v := range branches {
var (
base *gitlab.Commit
ahead int
behind int
)
if base, _, err = s.gitlab.MergeBase(c, project, []string{k, master}); err != nil {
return
}
// 计算领先commit
ahead = s.ComputeCommitNum(v, base, &tree)
// 计算落后commit
behind = s.ComputeCommitNum(branches[master], base, &tree)
log.Info("%s: ahead: %d behind:%d\n", k, ahead, behind)
branchDiff = append(branchDiff, &model.BranchDiffWithResponse{Branch: k, Behind: behind, Ahead: ahead, LatestUpdateTime: v.CreatedAt, LatestSyncTime: base.CreatedAt})
}
log.Info("Redis: Save Branch Diff Info Successfully!")
return
}
// ComputeCommitNum ...
func (s *Service) ComputeCommitNum(head *model.CommitTreeNode, base *gitlab.Commit, tree *[]*model.CommitTreeNode) (num int) {
var (
visitedQueue []string
commitTree = *tree
i int
)
if head.CommitID == base.ID {
return
}
visitedQueue = append(visitedQueue, head.CommitID)
for i = 0; ; i++ {
pointer := visitedQueue[i]
if pointer == base.ID {
break
}
for _, node := range commitTree {
if node.CommitID == pointer {
if utils.InSlice(base.ID, node.Parents) {
visitedQueue = append(visitedQueue, base.ID)
break
}
for _, p := range node.Parents {
if !utils.InSlice(p, visitedQueue) {
visitedQueue = append(visitedQueue, p)
}
}
break
}
}
if i > 999 {
break
}
if i == len(visitedQueue)-1 {
break
}
}
num = i
return
}
// AllMergeBase ...
func (s *Service) AllMergeBase(c context.Context, project int, branches []string, master string) (mergeBase map[string]*gitlab.Commit, err error) {
var base *gitlab.Commit
mergeBase = make(map[string]*gitlab.Commit)
for _, branch := range branches {
if base, _, err = s.gitlab.MergeBase(c, project, []string{master, branch}); err != nil {
return
}
mergeBase[branch] = base
}
return
}
//AllProjectBranchInfo 获取所有分支信息
func (s *Service) AllProjectBranchInfo(c context.Context, projectID int) (branches map[string]*model.CommitTreeNode, err error) {
var projectBranches []*model.StatisticsBranches
branches = make(map[string]*model.CommitTreeNode)
if projectBranches, err = s.dao.QueryProjectBranch(c, projectID); err != nil {
return
}
for _, b := range projectBranches {
var (
commitInfo *model.StatisticsCommits
commitParents []string
)
if commitInfo, err = s.dao.QueryCommitByID(projectID, b.CommitID); err != nil {
log.Error("AllProjectBranchInfo QueryCommitByID err(%+v)", err)
err = nil
continue
}
if commitInfo.ParentIDs != "" {
if err = json.Unmarshal([]byte(commitInfo.ParentIDs), &commitParents); err != nil {
return
}
}
branches[b.BranchName] = &model.CommitTreeNode{CommitID: b.CommitID, Parents: commitParents, CreatedAt: commitInfo.CreatedAt, Author: commitInfo.AuthorName}
}
return
}
// BuildCommitTree 获取到所有的Commits
func (s *Service) BuildCommitTree(c context.Context, project int) (tree []*model.CommitTreeNode, err error) {
var projectCommits []*model.StatisticsCommits
tree = []*model.CommitTreeNode{}
if projectCommits, err = s.dao.QueryProjectCommits(c, project); err != nil {
return
}
for _, c := range projectCommits {
var commitParents []string
commitParentsString := c.ParentIDs
if commitParentsString != "" {
if err = json.Unmarshal([]byte(commitParentsString), &commitParents); err != nil {
log.Error("解析commit parents报错(%+v)", err)
}
}
tree = append(tree, &model.CommitTreeNode{CommitID: c.CommitID, Parents: commitParents, CreatedAt: c.CreatedAt, Author: c.AuthorName})
}
return
}
/*-------------------------------------- sync branch ----------------------------------------*/
// SyncProjectBranch ...
func (s *Service) SyncProjectBranch(c context.Context, projectID int) (result *model.SyncResult, err error) {
var (
branches []*gitlab.Branch
resp *gitlab.Response
projectInfo *model.ProjectInfo
)
result = &model.SyncResult{}
if err = s.dao.DeleteProjectBranch(c, projectID); err != nil {
return
}
if projectInfo, err = s.dao.ProjectInfoByID(projectID); err != nil {
return
}
for page := 1; ; page++ {
result.TotalPage++
if branches, resp, err = s.gitlab.ListProjectBranch(projectID, page); err != nil {
return
}
for _, branch := range branches {
branchDB := &model.StatisticsBranches{
ProjectID: projectID,
ProjectName: projectInfo.Name,
CommitID: branch.Commit.ID,
BranchName: branch.Name,
Protected: branch.Protected,
Merged: branch.Merged,
DevelopersCanPush: branch.DevelopersCanPush,
DevelopersCanMerge: branch.DevelopersCanMerge,
}
if err = s.SaveDatabaseBranch(c, branchDB); err != nil {
log.Error("Branch 存数据库报错(%+V)", err)
err = nil
errData := &model.FailData{
ChildIDStr: branch.Name,
}
result.FailData = append(result.FailData, errData)
continue
}
result.TotalNum++
}
if resp.NextPage == 0 {
break
}
}
return
}
// SaveDatabaseBranch ...
func (s *Service) SaveDatabaseBranch(c context.Context, branchDB *model.StatisticsBranches) (err error) {
var (
total int
branch *model.StatisticsBranches
)
if total, err = s.dao.HasBranch(c, branchDB.ProjectID, branchDB.BranchName); err != nil {
log.Error("SaveDatabaseBranch HasBranch(%+v)", err)
return
}
// found only one, so update
if total == 1 {
if branch, err = s.dao.QueryFirstBranch(c, branchDB.ProjectID, branchDB.BranchName); err != nil {
log.Error("SaveDatabaseBranch QueryFirstBranch(%+v)", err)
return
}
branchDB.ID = branch.ID
if err = s.dao.UpdateBranch(c, branchDB.ProjectID, branchDB.BranchName, branchDB); err != nil {
log.Error("SaveDatabaseBranch UpdateBranch(%+v)", err)
return
}
return
} else if total > 1 {
// found repeated row, this situation will not exist under normal
log.Warn("SaveDatabaseBranch Branch has more rows(%d)", total)
return
}
// insert row now
if err = s.dao.CreateBranch(c, branchDB); err != nil {
log.Error("SaveDatabaseBranch CreateBranch(%+v)", err)
return
}
return
}
/*-------------------------------------- agg branch ----------------------------------------*/
// AggregateProjectBranch ...
func (s *Service) AggregateProjectBranch(c context.Context, projectID int, master string) (err error) {
var (
branches map[string]*model.CommitTreeNode
tree []*model.CommitTreeNode
projectInfo *model.ProjectInfo
)
if err = s.dao.DeleteAggregateBranch(c, projectID); err != nil {
return
}
if projectInfo, err = s.dao.ProjectInfoByID(projectID); err != nil {
return
}
if branches, err = s.AllProjectBranchInfo(c, projectID); err != nil {
return
}
if tree, err = s.BuildCommitTree(c, projectID); err != nil {
return
}
for k, v := range branches {
var (
base *gitlab.Commit
ahead int
behind int
)
if base, _, err = s.gitlab.MergeBase(c, projectID, []string{k, master}); err != nil {
return
}
// 计算领先commit
ahead = s.ComputeCommitNum(v, base, &tree)
// 计算落后commit
behind = s.ComputeCommitNum(branches[master], base, &tree)
branch := &model.AggregateBranches{
ProjectID: projectID,
ProjectName: projectInfo.Name,
BranchName: k,
BranchUserName: v.Author,
BranchMaster: master,
Behind: behind,
Ahead: ahead,
LatestUpdateTime: v.CreatedAt,
LatestSyncTime: base.CreatedAt,
}
if err = s.SaveAggregateBranchDatabase(c, branch); err != nil {
log.Error("AggregateBranch 存数据库报错(%+V)", err)
continue
}
}
return
}
// SaveAggregateBranchDatabase ...
func (s *Service) SaveAggregateBranchDatabase(c context.Context, branchDB *model.AggregateBranches) (err error) {
var (
total int
aggregateBranch *model.AggregateBranches
)
if total, err = s.dao.HasAggregateBranch(c, branchDB.ProjectID, branchDB.BranchName); err != nil {
log.Error("SaveAggregateBranchDatabase HasAggregateBranch(%+v)", err)
return
}
// found only one, so update
if total == 1 {
if aggregateBranch, err = s.dao.QueryFirstAggregateBranch(c, branchDB.ProjectID, branchDB.BranchName); err != nil {
log.Error("SaveDatabaseBranch QueryFirstAggregateBranch(%+v)", err)
return
}
branchDB.ID = aggregateBranch.ID
if err = s.dao.UpdateAggregateBranch(c, branchDB.ProjectID, branchDB.BranchName, branchDB); err != nil {
log.Error("SaveAggregateBranchDatabase UpdateAggregateBranch(%+v)", err)
return
}
return
} else if total > 1 {
// found repeated row, this situation will not exist under normal
log.Warn("SaveAggregateBranchDatabase AggregateBranch has more rows(%d)", total)
return
}
// insert row now
if err = s.dao.CreateAggregateBranch(c, branchDB); err != nil {
log.Error("SaveAggregateBranchDatabase CreateAggregateBranch(%+v)", err)
return
}
return
}

View File

@@ -0,0 +1,198 @@
package service
import (
"context"
"strconv"
"strings"
"go-common/app/admin/ep/saga/conf"
"go-common/app/admin/ep/saga/model"
"go-common/library/log"
"github.com/xanzy/go-gitlab"
)
// collectprojectproc cron func
func (s *Service) collectprojectproc() {
/*defer func() {
if x := recover(); x != nil {
log.Error("collectprojectproc panic(%v)", errors.WithStack(fmt.Errorf("%v", x)))
go s.collectprojectproc()
log.Info("collectprojectproc recover")
}
}()*/
var err error
if err = s.CollectProject(context.TODO()); err != nil {
log.Error("s.CollectProject err (%+v)", err)
}
}
// CollectProject collect project information
func (s *Service) CollectProject(c context.Context) (err error) {
var (
projects []*gitlab.Project
total = 0
page = 1
)
log.Info("Collect Project start")
for page <= 1000 {
if projects, err = s.gitlab.ListProjects(page); err != nil {
return
}
num := len(projects)
if num <= 0 {
break
}
total = total + num
for _, p := range projects {
if err = s.insertDB(p); err != nil {
return
}
}
page = page + 1
}
log.Info("Collect Project end, find %d projects", total)
return
}
// insertDB
func (s *Service) insertDB(project *gitlab.Project) (err error) {
var (
b bool
parseFail bool
projectInfo = &model.ProjectInfo{
ProjectID: project.ID,
Name: project.Name,
Description: project.Description,
WebURL: project.WebURL,
Repo: project.SSHURLToRepo,
DefaultBranch: project.DefaultBranch,
//Owner: project.Owner.Name,
SpaceName: project.Namespace.Name,
SpaceKind: project.Namespace.Kind,
Saga: false,
Runner: false,
Department: "",
Business: "",
Language: "",
}
)
if project.Namespace.Kind == "user" {
return
}
if b, err = s.dao.HasProjectInfo(project.ID); err != nil {
return
}
if len(project.Description) > 6 {
projectInfo.Department, projectInfo.Business, projectInfo.Language, parseFail = parseDes(project.Description)
}
if parseFail {
projectInfo.Department, projectInfo.Business = parseGroup(project.Namespace.Name)
}
if b {
/*if err = s.update(project.ID, projectInfo); err != nil {
return
}*/
if err = s.dao.UpdateProjectInfo(project.ID, projectInfo); err != nil {
log.Warn("UpdateProjectInfo ProjectID(%d), Description: (%s)", projectInfo.ProjectID, projectInfo.Description)
if strings.Contains(err.Error(), "Incorrect string value") {
projectInfo.Description = strconv.QuoteToASCII(projectInfo.Description)
}
if err = s.dao.UpdateProjectInfo(project.ID, projectInfo); err != nil {
return
}
}
} else {
if err = s.dao.AddProjectInfo(projectInfo); err != nil {
log.Warn("AddProjectInfo ProjectID(%d), Description: (%s)", projectInfo.ProjectID, projectInfo.Description)
if strings.Contains(err.Error(), "Incorrect string value") {
projectInfo.Description = strconv.QuoteToASCII(projectInfo.Description)
}
if err = s.dao.AddProjectInfo(projectInfo); err != nil {
return
}
}
}
return
}
// update database
/*func (s *Service) update(projectID int, projectSrc *model.ProjectInfo) (err error) {
var (
projectDes *model.ProjectInfo
)
if projectDes, err = s.dao.ProjectInfoByID(projectID); err != nil {
return
}
if *projectSrc == *projectDes {
return
}
s.dao.UpdateProjectInfo(projectID, projectSrc)
return
}*/
// parseDes get info from project description
func parseDes(s string) (department, business, language string, parseFail bool) {
//[主站 android java]
ids := strings.Index(s, "[")
idx := strings.LastIndex(s, "]")
if ids == -1 || idx == -1 {
parseFail = true
return
}
str := s[ids+1 : idx]
fields := strings.Fields(str)
if len(fields) < 3 {
parseFail = true
return
}
department = fields[0]
business = fields[1]
language = fields[2]
for _, de := range conf.Conf.Property.DeInfo {
if department == de.Label {
department = de.Value
}
}
for _, bu := range conf.Conf.Property.BuInfo {
if business == bu.Label {
business = bu.Value
}
}
return
}
func parseGroup(s string) (department, business string) {
group := strings.Fields(conf.Conf.Property.Group.Name)
de := strings.Fields(conf.Conf.Property.Group.Department)
bu := strings.Fields(conf.Conf.Property.Group.Business)
for i := 0; i < len(group); i++ {
if s == group[i] {
department, business = de[i], bu[i]
}
}
return
}

View File

@@ -0,0 +1,234 @@
package service
import (
"context"
"encoding/json"
"strconv"
"strings"
"time"
"go-common/app/admin/ep/saga/model"
"go-common/app/admin/ep/saga/service/utils"
"go-common/library/log"
"github.com/xanzy/go-gitlab"
)
// QueryProjectCommit query project commit info according to project id.
func (s *Service) QueryProjectCommit(c context.Context, req *model.ProjectDataReq) (resp *model.ProjectDataResp, err error) {
if resp, err = s.QueryProject(c, "commit", req); err != nil {
return
}
return
}
// QueryTeamCommit query team commit info according to department and business
func (s *Service) QueryTeamCommit(c context.Context, req *model.TeamDataRequest) (resp *model.TeamDataResp, err error) {
if resp, err = s.QueryTeam(c, "commit", req); err != nil {
return
}
return
}
// QueryCommit query commit info according to department、 business and time.
func (s *Service) QueryCommit(c context.Context, req *model.CommitRequest) (resp *model.CommitResp, err error) {
var (
layout = "2006-01-02"
projectInfo []*model.ProjectInfo
reqProject = &model.ProjectInfoRequest{}
ProjectCommit []*model.ProjectCommit
respCommit *gitlab.Response
since time.Time
until time.Time
commitNum int
)
if len(req.Department) <= 0 && len(req.Business) <= 0 {
log.Warn("query department and business are empty!")
return
}
reqProject.Department = req.Department
reqProject.Business = req.Business
reqProject.Username = req.Username
if _, projectInfo, err = s.dao.QueryProjectInfo(false, reqProject); err != nil {
return
}
if len(projectInfo) <= 0 {
log.Warn("Found no project!")
return
}
//since, err = time.Parse("2006-01-02 15:04:05", "2018-08-13 00:00:00")
if since, err = time.ParseInLocation(layout, req.Since, time.Local); err != nil {
return
}
if until, err = time.ParseInLocation(layout, req.Until, time.Local); err != nil {
return
}
log.Info("query commit start!")
for _, project := range projectInfo {
if _, respCommit, err = s.gitlab.ListProjectCommit(project.ProjectID, 1, &since, &until); err != nil {
return
}
//log.Info("query: %s, result: %+v", project.Name, respCommit)
CommitPer := &model.ProjectCommit{
ProjectID: project.ProjectID,
Name: project.Name,
CommitNum: respCommit.TotalItems,
}
ProjectCommit = append(ProjectCommit, CommitPer)
commitNum = commitNum + respCommit.TotalItems
}
log.Info("query commit end!")
resp = &model.CommitResp{
Total: commitNum,
ProjectCommit: ProjectCommit,
}
return
}
/*-------------------------------------- sync commit ----------------------------------------*/
// SyncProjectCommit ...
func (s *Service) SyncProjectCommit(projectID int) (result *model.SyncResult, err error) {
var (
//syncAllTime = conf.Conf.Property.SyncData.SyncAllTime
syncAllTime = false
commits []*gitlab.Commit
resp *gitlab.Response
since *time.Time
until *time.Time
projectInfo *model.ProjectInfo
)
result = &model.SyncResult{}
if projectInfo, err = s.dao.ProjectInfoByID(projectID); err != nil {
return
}
if !syncAllTime {
since, until = utils.CalSyncTime()
}
log.Info("sync project(%d) commit time since: %v, until: %v", projectID, since, until)
for page := 1; ; page++ {
result.TotalPage++
if commits, resp, err = s.gitlab.ListProjectCommit(projectID, page, since, until); err != nil {
return
}
for _, commit := range commits {
var (
statsAdditions int
statsDeletions int
parentIDs string
commitStatus string
)
if commit.Stats != nil {
statsAdditions = commit.Stats.Additions
statsDeletions = commit.Stats.Deletions
}
if commit.Status != nil {
commitStatusByte, _ := json.Marshal(commit.Status)
commitStatus = string(commitStatusByte)
}
parentIDsByte, _ := json.Marshal(commit.ParentIDs)
parentIDs = string(parentIDsByte)
commitDB := &model.StatisticsCommits{
CommitID: commit.ID,
ProjectID: projectID,
ProjectName: projectInfo.Name,
ShortID: commit.ShortID,
Title: commit.Title,
AuthorName: commit.AuthorName,
AuthoredDate: commit.AuthoredDate,
CommitterName: commit.CommitterName,
CommittedDate: commit.CommittedDate,
CreatedAt: commit.CreatedAt,
Message: commit.Message,
ParentIDs: parentIDs,
StatsAdditions: statsAdditions,
StatsDeletions: statsDeletions,
Status: commitStatus,
}
if len(commitDB.Message) > model.MessageMaxLen {
commitDB.Message = commitDB.Message[0 : model.MessageMaxLen-1]
}
if err = s.SaveDatabaseCommit(commitDB); err != nil {
log.Error("Commit Save Database err: projectID(%d), commitID(%s)", projectID, commit.ID)
err = nil
errData := &model.FailData{
ChildIDStr: commit.ID,
}
result.FailData = append(result.FailData, errData)
continue
}
result.TotalNum++
}
if resp.NextPage == 0 {
break
}
}
return
}
// SaveDatabaseCommit ...
func (s *Service) SaveDatabaseCommit(commitDB *model.StatisticsCommits) (err error) {
var total int
if total, err = s.dao.HasCommit(commitDB.ProjectID, commitDB.CommitID); err != nil {
log.Error("SaveDatabaseCommit HasCommit(%+v)", err)
return
}
// found only one, so update
if total == 1 {
if err = s.dao.UpdateCommit(commitDB.ProjectID, commitDB.CommitID, commitDB); err != nil {
if strings.Contains(err.Error(), model.DatabaseErrorText) {
commitDB.Title = strconv.QuoteToASCII(commitDB.Title)
commitDB.Message = strconv.QuoteToASCII(commitDB.Message)
commitDB.Title = utils.Unicode2Chinese(commitDB.Title)
commitDB.Message = utils.Unicode2Chinese(commitDB.Message)
}
if err = s.dao.UpdateCommit(commitDB.ProjectID, commitDB.CommitID, commitDB); err != nil {
log.Error("SaveDatabaseCommit UpdateCommit err(%+v)", err)
return
}
}
return
} else if total > 1 {
// found repeated row, this situation will not exist under normal
log.Warn("SaveDatabaseCommit commit has more rows(%d)", total)
return
}
// insert row now
if err = s.dao.CreateCommit(commitDB); err != nil {
if strings.Contains(err.Error(), model.DatabaseErrorText) {
commitDB.Title = strconv.QuoteToASCII(commitDB.Title)
commitDB.Message = strconv.QuoteToASCII(commitDB.Message)
commitDB.Title = utils.Unicode2Chinese(commitDB.Title)
commitDB.Message = utils.Unicode2Chinese(commitDB.Message)
}
if err = s.dao.CreateCommit(commitDB); err != nil {
log.Error("SaveDatabaseCommit CreateCommit err(%+v)", err)
return
}
}
return
}

View File

@@ -0,0 +1,518 @@
package service
import (
"context"
"encoding/json"
"fmt"
"net/url"
"reflect"
"strconv"
"strings"
"time"
"go-common/app/admin/ep/saga/conf"
"go-common/app/admin/ep/saga/model"
"go-common/library/ecode"
"go-common/library/log"
"github.com/BurntSushi/toml"
)
const (
_configFlag = "\r\n"
_sagaConfigFlag = "[[property.repos]]"
)
const (
_svenConfigAppName = "app_name"
_svenConfigEnv = "env"
_svenConfigZone = "zone"
_svenConfigTreeID = "tree_id"
_svenConfigToken = "token"
_svenConfigBuild = "build"
_svenConfigUser = "user"
_svenConfigData = "data"
_svenConfigNames = "names"
_svenConfigMark = "mark"
_svenConfigConfigIDs = "config_ids"
_svenConfigForce = "force"
_svenConfigIncrement = "increment"
)
const (
_formatStr = ` %s=%s`
_formatStrQuo = ` %s="%s"`
_formatValue = ` %s=%v`
_formatInt = ` %s=%d`
)
const (
_defaultBranch = "master"
_defaultLockTimeout = 600
)
const (
_repoURL = "URL"
_repoGroup = "Group"
_repoName = "Name"
_repoLanguage = "Language"
_repoLockTimeout = "LockTimeout"
_repoAuthBranches = "AuthBranches"
_repoTargetBranches = "TargetBranches"
)
var (
sagaConfigCnName = []string{
"仓库地址",
"仓库组名",
"仓库名称",
"仓库别名",
"开发语言",
"权限分支",
"目标分支",
"MR锁定超时时间(s)",
"最少review人数",
"是否关联pipeline",
"自动合并",
"权限限制",
"准入标签",
"超级权限用户",
}
sagaConfigMark = []string{
"仓库地址",
"仓库组名",
"仓库名称",
"仓库别名",
"仓库使用语言",
"saga的权限管控将以此分支配置的CONTRIBUTORS.md为准即使CONTRIBUTORS.md在其他分支上更改了也都会以此分支的鉴权信息为准。",
"配置的分支可以触发saga行为如配置 targetBranches为master、release分支则MR的目标分支为master或release时都能触发saga行为。支持通配",
"每个仓库在每个时间点只能允许一个MR在合并。MR合并时会取得一个锁并在其合并结束后将其释放如果获取的锁MR在lockTimeout时间内都未能结束则会认为超时并将锁自动释放这样其他MR才能有机会获取到独享锁及合并的机会。",
"最终合并前除了需要owner点赞外还需通过权限文件配置的Reviewer中minReviewer数量的人点赞后可合并。",
"配置后saga将会检查pipeline执行结果并会在最后merge前再次retry pipeline。不配置的话saga不会对pipeline执行结果进行判断。",
"此配置以 relatePipeline 为基础打开后saga在MR最终合并前将不再retry pipeline并且 +mr 时如果pipeline还在运行中待pipeline运行通过后MR将会自动合并。",
"打开后owner的权限将只限定在当前目录即如果子目录配置了owner等信息根目录的owner等将不能再管控子目录。",
"如果配置了标签saga只合入打了此label的MR。",
"如果有配置原来的鉴权文件CONTRIBUTORS.md将会失效需要合并的MR都必须通过super users的review。super users本身也拥有+mr直接合并的权利。",
}
)
// SagaUserList ...
func (s *Service) SagaUserList(c context.Context) (resp []string, err error) {
resp = conf.Conf.Property.Sven.SagaConfigsParam.UserList
return
}
// QueryAllConfigFile ...
func (s *Service) QueryAllConfigFile(c context.Context, sessionID string, isSaga bool) (resp *model.ConfigData, err error) {
var (
url = conf.Conf.Property.Sven.Configs + "?app_name=%s&tree_id=%s&env=%s&zone=%s&build_id=%s"
sagaConfig = conf.Conf.Property.Sven.SagaConfigsParam
runnerConfig = conf.Conf.Property.Sven.ConfigsParam
)
if isSaga {
url = fmt.Sprintf(url, sagaConfig.AppName, strconv.Itoa(sagaConfig.TreeID), sagaConfig.Env, sagaConfig.Zone, strconv.Itoa(sagaConfig.BuildId))
} else {
url = fmt.Sprintf(url, runnerConfig.AppName, strconv.Itoa(runnerConfig.TreeID), runnerConfig.Env, runnerConfig.Zone, strconv.Itoa(runnerConfig.BuildId))
}
return s.dao.QueryAllConfigFile(c, sessionID, url)
}
// QueryConfigFileContent ...
func (s *Service) QueryConfigFileContent(c context.Context, sessionID string) (content string, err error) {
var (
url = conf.Conf.Property.Sven.ConfigValue
fileName = conf.Conf.Property.Sven.SagaConfigsParam.FileName
configs *model.ConfigData
)
if configs, err = s.QueryAllConfigFile(c, sessionID, true); err != nil {
return
}
for _, confValue := range configs.BuildFiles {
if confValue.Name == fileName {
log.Info("QueryConfigFileContent get config name: %s", fileName)
id := strconv.Itoa(confValue.ID)
url = fmt.Sprintf(url+"?config_id=%s", id)
if content, err = s.dao.QueryConfigFileContent(c, sessionID, url); err != nil {
return
}
}
}
return
}
// QueryProjectSagaConfig ...
func (s *Service) QueryProjectSagaConfig(c context.Context, sessionID string, projectID int) (sagaConfig *model.RepoConfig, err error) {
var (
projectInfo *model.ProjectInfo
content string
)
if content, err = s.QueryConfigFileContent(c, sessionID); err != nil {
return
}
index := strings.Index(content, _sagaConfigFlag)
if index < 0 {
return
}
content = content[index:]
log.Info("QueryProjectSagaConfig content: %s", content)
Conf := &model.Config{}
if _, err = toml.Decode(content, &Conf); err != nil {
log.Error("QueryProjectSagaConfig Decode err(%+v)", err)
return
}
if projectInfo, err = s.dao.ProjectInfoByID(projectID); err != nil {
log.Error("QueryProjectSagaConfig ProjectInfoByID err(%+v)", err)
return
}
projectUrl := strings.Replace(projectInfo.Repo, "git-test", "git", 1)
for _, r := range Conf.Property.Repos {
if r.URL == projectUrl {
sagaConfig = r
return
}
}
return
}
// UpdateConfig ...
func (s *Service) UpdateConfig(c context.Context, sessionID, user, configFileName, configContent, mark string, isSaga bool) (resp *model.CommonResp, err error) {
var (
reqUrl = conf.Conf.Property.Sven.ConfigUpdate
params = url.Values{}
)
if isSaga {
params.Set(_svenConfigAppName, conf.Conf.Property.Sven.SagaConfigsParam.AppName)
params.Set(_svenConfigEnv, conf.Conf.Property.Sven.SagaConfigsParam.Env)
params.Set(_svenConfigZone, conf.Conf.Property.Sven.SagaConfigsParam.Zone)
params.Set(_svenConfigTreeID, strconv.Itoa(conf.Conf.Property.Sven.SagaConfigsParam.TreeID))
params.Set(_svenConfigToken, conf.Conf.Property.Sven.SagaConfigsParam.Token)
} else {
params.Set(_svenConfigAppName, conf.Conf.Property.Sven.ConfigsParam.AppName)
params.Set(_svenConfigEnv, conf.Conf.Property.Sven.ConfigsParam.Env)
params.Set(_svenConfigZone, conf.Conf.Property.Sven.ConfigsParam.Zone)
params.Set(_svenConfigTreeID, strconv.Itoa(conf.Conf.Property.Sven.ConfigsParam.TreeID))
params.Set(_svenConfigToken, conf.Conf.Property.Sven.ConfigsParam.Token)
}
data := `[{"name":"%s","comment":"%s","mark":"%s"}]`
data = fmt.Sprintf(data, configFileName, configContent, mark)
params.Set(_svenConfigData, data)
params.Set(_svenConfigUser, user)
log.Info("UpdateConfig params:%v", params)
if resp, err = s.dao.RequestConfig(c, sessionID, reqUrl, params); err != nil {
return
}
if resp.Code == ecode.OK.Code() && resp.Message == "0" {
log.Info("RequestConfig success")
resp = nil
}
return
}
// PublicConfig ...
func (s *Service) PublicConfig(c context.Context, sessionID, user, configFileName, mark string, isSaga bool) (resp *model.CommonResp, err error) {
var (
reqUrl = conf.Conf.Property.Sven.TagUpdate
params = url.Values{}
)
if isSaga {
params.Set(_svenConfigAppName, conf.Conf.Property.Sven.SagaConfigsParam.AppName)
params.Set(_svenConfigEnv, conf.Conf.Property.Sven.SagaConfigsParam.Env)
params.Set(_svenConfigZone, conf.Conf.Property.Sven.SagaConfigsParam.Zone)
params.Set(_svenConfigTreeID, strconv.Itoa(conf.Conf.Property.Sven.SagaConfigsParam.TreeID))
params.Set(_svenConfigToken, conf.Conf.Property.Sven.SagaConfigsParam.Token)
params.Set(_svenConfigBuild, conf.Conf.Property.Sven.SagaConfigsParam.Build)
} else {
params.Set(_svenConfigAppName, conf.Conf.Property.Sven.ConfigsParam.AppName)
params.Set(_svenConfigEnv, conf.Conf.Property.Sven.ConfigsParam.Env)
params.Set(_svenConfigZone, conf.Conf.Property.Sven.ConfigsParam.Zone)
params.Set(_svenConfigTreeID, strconv.Itoa(conf.Conf.Property.Sven.ConfigsParam.TreeID))
params.Set(_svenConfigToken, conf.Conf.Property.Sven.ConfigsParam.Token)
params.Set(_svenConfigBuild, conf.Conf.Property.Sven.ConfigsParam.Build)
}
params.Set(_svenConfigForce, strconv.Itoa(conf.Conf.Property.Sven.SagaConfigsParam.Force))
params.Set(_svenConfigIncrement, strconv.Itoa(conf.Conf.Property.Sven.SagaConfigsParam.Increment))
params.Set(_svenConfigMark, mark)
params.Set(_svenConfigUser, user)
params.Set(_svenConfigNames, configFileName)
params.Set(_svenConfigConfigIDs, "")
if resp, err = s.dao.RequestConfig(c, sessionID, reqUrl, params); err != nil {
return
}
if resp.Code == ecode.OK.Code() && resp.Message == "0" {
log.Info("RequestConfig success")
resp = nil
}
return
}
// ParseRequestConfig ...
func (s *Service) ParseRequestConfig(projectInfo *model.ProjectInfo, configs []model.ConfigSagaItem) (requestConfig *model.RepoConfig, requestConfigStr string, err error) {
var (
configStr string
content []byte
)
configStr = doBasicDefault(projectInfo)
for _, config := range configs {
rv := reflect.ValueOf(config.Value)
if !rv.IsValid() {
configStr, _ = doDefault(config.Name, configStr)
continue
}
if rv.Kind() == reflect.Slice {
log.Info("%s is slice", config.Name)
if rv.IsNil() {
configStr, _ = doDefault(config.Name, configStr)
continue
}
if content, err = json.Marshal(config.Value); err != nil {
log.Error("ParseRequestConfig err(%+v)", err)
return
}
configTr := fmt.Sprintf(_formatStr, config.Name, string(content))
configStr = configStr + configTr + _configFlag
} else {
configTr := fmt.Sprintf(_formatValue, config.Name, config.Value)
configStr = configStr + configTr + _configFlag
}
}
log.Info("ParseRequestConfig: %s", configStr)
requestConfigStr = configStr
requestConfig = &model.RepoConfig{}
if _, err = toml.Decode(configStr, &requestConfig); err != nil {
log.Error("ParseRequestConfig toml decode err(%+v)", err)
return
}
return
}
// doBasicDefault ...
func doBasicDefault(projectInfo *model.ProjectInfo) (configStr string) {
var configTr string
configStr = _configFlag
configTr = fmt.Sprintf(_formatStrQuo, _repoURL, projectInfo.Repo)
configStr = configStr + configTr + _configFlag
configTr = fmt.Sprintf(_formatStrQuo, _repoGroup, projectInfo.SpaceName)
configStr = configStr + configTr + _configFlag
configTr = fmt.Sprintf(_formatStrQuo, _repoName, projectInfo.Name)
configStr = configStr + configTr + _configFlag
return configStr
}
// doDefault ...
func doDefault(name, config string) (configStr string, err error) {
var (
content []byte
defaultBr = []string{_defaultBranch}
)
configStr = config
if strings.ToLower(name) == strings.ToLower(_repoLockTimeout) {
configTr := fmt.Sprintf(_formatInt, name, _defaultLockTimeout)
configStr = configStr + configTr + _configFlag
}
if strings.ToLower(name) == strings.ToLower(_repoAuthBranches) || strings.ToLower(name) == strings.ToLower(_repoTargetBranches) {
if content, err = json.Marshal(defaultBr); err != nil {
log.Error("Marshal err(%+v)", err)
return
}
configTr := fmt.Sprintf(_formatStr, name, string(content))
configStr = configStr + configTr + _configFlag
}
return
}
// ParseSvenConfig ...
func (s *Service) ParseSvenConfig(c context.Context, sessionID, projectUrl string) (fileContent, svenConfig string, err error) {
var (
content string
projectConfigs []string
)
if fileContent, err = s.QueryConfigFileContent(c, sessionID); err != nil {
return
}
log.Info("ParseSvenConfig fileContent : %s", fileContent)
index := strings.Index(fileContent, _sagaConfigFlag)
if index < 0 {
log.Warn("ParseSvenConfig not found any config flag: %s", projectUrl)
return
}
content = fileContent[index:]
projectConfigs = strings.Split(content, _sagaConfigFlag)
for i := 0; i < len(projectConfigs); i++ {
if strings.Contains(projectConfigs[i], projectUrl) {
svenConfig = projectConfigs[i]
return
}
}
return
}
// ReplaceConfig ...
func (s *Service) ReplaceConfig(c context.Context, username, sessionID string, projectInfo *model.ProjectInfo, req *model.ConfigList) (newConfig string, err error) {
var (
requestConfig *model.RepoConfig
requestConfigStr string
fileContent string
svenConfig string
)
if requestConfig, requestConfigStr, err = s.ParseRequestConfig(projectInfo, req.Configs); err != nil {
return
}
if fileContent, svenConfig, err = s.ParseSvenConfig(c, sessionID, projectInfo.Repo); err != nil {
return
}
if len(svenConfig) <= 0 {
return
}
index := strings.Index(svenConfig, "#")
if index > 0 {
annotate := svenConfig[index:]
requestConfigStr = requestConfigStr + _configFlag + " " + annotate
}
log.Info("ReplaceConfig requestConfig: %v", requestConfig)
log.Info("ReplaceConfig requestConfigStr: %s", requestConfigStr)
log.Info("ReplaceConfig svenConfig: %s", svenConfig)
newConfig = strings.Replace(fileContent, svenConfig, requestConfigStr, 1)
log.Info("ReplaceConfig newConfig: %s", newConfig)
return
}
// ReleaseSagaConfig ...
func (s *Service) ReleaseSagaConfig(c context.Context, username, sessionID string, req *model.ConfigList) (resp *model.CommonResp, err error) {
var (
configFileName = conf.Conf.Property.Sven.SagaConfigsParam.FileName
sagaConfig *model.RepoConfig
projectInfo *model.ProjectInfo
projectID = req.ProjectID
newConfigContent string
)
if sagaConfig, err = s.QueryProjectSagaConfig(c, sessionID, req.ProjectID); err != nil || sagaConfig == nil {
log.Error("ReleaseSagaConfig err(%+v)", err)
return
}
if projectInfo, err = s.dao.ProjectInfoByID(projectID); err != nil {
log.Error("ProjectInfoByID err(%+v)", err)
return
}
projectInfo.Repo = strings.Replace(projectInfo.Repo, "git-test", "git", 1)
log.Info("ReleaseSagaConfig query project: %s, sagaConfig: %v", projectInfo.Name, sagaConfig)
if sagaConfig.Name == projectInfo.Name {
if newConfigContent, err = s.ReplaceConfig(c, username, sessionID, projectInfo, req); err != nil {
return
}
year, month, day := time.Now().Date()
monthInt := int(month)
hour := time.Now().Hour()
updateMark := fmt.Sprintf("%s-%d-%d-%d-%d | from saga-admin", username, year, monthInt, day, hour)
newConfigContent = strconv.Quote(newConfigContent)[1 : len(strconv.Quote(newConfigContent))-1]
log.Info("ReleaseSagaConfig newConfig: %s", newConfigContent)
if _, err = s.UpdateConfig(c, sessionID, username, configFileName, newConfigContent, updateMark, true); err != nil {
log.Error("UpdateConfig err(%+v)", err)
return
}
if _, err = s.PublicConfig(c, sessionID, username, configFileName, updateMark, true); err != nil {
log.Error("PublicConfig err(%+v)", err)
return
}
}
return
}
// OptionSaga ...
func (s *Service) OptionSaga(c context.Context, projectID, sessionID string) (resp []*model.OptionSagaItem, err error) {
var sagaConfig *model.RepoConfig
projectIDInt, _ := strconv.Atoi(projectID)
if sagaConfig, err = s.QueryProjectSagaConfig(c, sessionID, projectIDInt); err != nil || sagaConfig == nil {
log.Error("QueryProjectSagaConfig err(%+v)", err)
return
}
t := reflect.TypeOf(sagaConfig)
if t.Kind() != reflect.Ptr {
log.Info("OptionSaga the object is not a Ptr, but it is : %v", t.Kind())
return
}
t = reflect.TypeOf(sagaConfig).Elem()
if t.Kind() != reflect.Struct {
log.Info("OptionSaga the object is not a struct, but it is : %v", t.Kind())
return
}
v := reflect.ValueOf(sagaConfig).Elem()
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
if f.Name == _repoURL || f.Name == _repoGroup || f.Name == _repoName || f.Name == _repoLanguage {
continue
}
val := v.Field(i).Interface()
log.Info("OptionSaga === %s: %v = %v", f.Name, f.Type, val)
sagaItem := &model.OptionSagaItem{}
sagaItem.Name = f.Name
sagaItem.Value = val
sagaItem.CNName = sagaConfigCnName[i]
sagaItem.Remark = sagaConfigMark[i]
configTr := fmt.Sprintf(`%v`, f.Type)
sagaItem.Type = configTr
resp = append(resp, sagaItem)
}
return
}

View File

@@ -0,0 +1,485 @@
package service
import (
"context"
"encoding/json"
"strconv"
"strings"
"time"
"go-common/app/admin/ep/saga/model"
"go-common/app/admin/ep/saga/service/utils"
"go-common/library/log"
"github.com/xanzy/go-gitlab"
)
/*-------------------------------------- sync issue ----------------------------------------*/
// SyncAllIssues ...
func (s *Service) SyncAllIssues(projectID int) (totalPage, totalNum int, err error) {
var (
//syncAllTime = conf.Conf.Property.SyncData.SyncAllTime
syncAllTime = false
issues []*gitlab.Issue
resp *gitlab.Response
since *time.Time
until *time.Time
)
if !syncAllTime {
since, until = utils.CalSyncTime()
}
for page := 1; ; page++ {
totalPage++
if issues, resp, err = s.gitlab.ListProjectIssues(projectID, page, since, until); err != nil {
return
}
for _, issue := range issues {
var (
issueAssignees string
issueLabels string
issueTimeStats string
milestoneID int
)
if issue.Milestone != nil {
milestoneID = issue.Milestone.ID
}
issueAssigneesByte, _ := json.Marshal(issue.Assignees)
issueAssignees = string(issueAssigneesByte)
issueLabelsByte, _ := json.Marshal(issue.Labels)
issueLabels = string(issueLabelsByte)
issueTimeStatsByte, _ := json.Marshal(issue.TimeStats)
issueTimeStats = string(issueTimeStatsByte)
issueDB := &model.StatisticsIssues{
ProjectID: projectID,
IssueID: issue.ID,
IssueIID: issue.IID,
MilestoneID: milestoneID,
Description: issue.Description,
State: issue.State,
Assignees: issueAssignees,
Upvotes: issue.Upvotes,
Downvotes: issue.Downvotes,
Labels: issueLabels,
Title: issue.Title,
UpdatedAt: issue.UpdatedAt,
CreatedAt: issue.CreatedAt,
ClosedAt: issue.ClosedAt,
Subscribed: issue.Subscribed,
UserNotesCount: issue.UserNotesCount,
DueDate: issue.DueDate,
WebURL: issue.WebURL,
TimeStats: issueTimeStats,
Confidential: issue.Confidential,
Weight: issue.Weight,
DiscussionLocked: issue.DiscussionLocked,
IssueLinkID: issue.IssueLinkID,
}
if issue.Author != nil {
issueDB.AuthorID = issue.Author.ID
issueDB.AuthorName = issue.Author.Name
}
if issue.Assignee != nil {
issueDB.AssigneeID = issue.Assignee.ID
issueDB.AssigneeName = issue.Assignee.Name
}
if len(issueDB.Description) > model.MessageMaxLen {
issueDB.Description = issueDB.Description[0 : model.MessageMaxLen-1]
}
if err = s.SaveDatabaseIssue(issueDB); err != nil {
log.Error("issue Save Database err: projectID(%d), IssueID(%d)", projectID, issue.ID)
err = nil
continue
}
totalNum++
}
if resp.NextPage == 0 {
break
}
}
return
}
// SaveDatabaseIssue ...
func (s *Service) SaveDatabaseIssue(issueDB *model.StatisticsIssues) (err error) {
var total int
if total, err = s.dao.HasIssue(issueDB.ProjectID, issueDB.IssueID); err != nil {
log.Error("SaveDatabaseIssue HasIssue(%+v)", err)
return
}
// update found row
if total == 1 {
if err = s.dao.UpdateIssue(issueDB.ProjectID, issueDB.IssueID, issueDB); err != nil {
if strings.Contains(err.Error(), model.DatabaseErrorText) {
issueDB.Title = strconv.QuoteToASCII(issueDB.Title)
issueDB.Description = strconv.QuoteToASCII(issueDB.Description)
issueDB.Title = utils.Unicode2Chinese(issueDB.Title)
issueDB.Description = utils.Unicode2Chinese(issueDB.Description)
}
if err = s.dao.UpdateIssue(issueDB.ProjectID, issueDB.IssueID, issueDB); err != nil {
log.Error("SaveDatabaseIssue UpdateIssue(%+v)", err)
return
}
}
return
} else if total > 1 {
// found repeated row, this situation will not exist under normal
log.Warn("SaveDatabaseIssue issue has more rows(%d)", total)
return
}
// insert row now
if err = s.dao.CreateIssue(issueDB); err != nil {
if strings.Contains(err.Error(), model.DatabaseErrorText) {
issueDB.Title = strconv.QuoteToASCII(issueDB.Title)
issueDB.Description = strconv.QuoteToASCII(issueDB.Description)
issueDB.Title = utils.Unicode2Chinese(issueDB.Title)
issueDB.Description = utils.Unicode2Chinese(issueDB.Description)
}
if err = s.dao.CreateIssue(issueDB); err != nil {
log.Error("SaveDatabaseIssue CreateIssue(%+v)", err)
return
}
}
return
}
/*-------------------------------------- sync note ----------------------------------------*/
// SyncProjectNotes ...
func (s *Service) SyncProjectNotes(c context.Context, projectID int) (totalPage, totalNum int, err error) {
var (
//syncAllTime = conf.Conf.Property.SyncData.SyncAllTime
syncAllTime = false
resp *gitlab.Response
projectInfo *model.ProjectInfo
mrs []*model.StatisticsMrs
notes []*gitlab.Note
since *time.Time
until *time.Time
)
if projectInfo, err = s.dao.ProjectInfoByID(projectID); err != nil {
return
}
if !syncAllTime {
since, until = utils.CalSyncTime()
}
if mrs, err = s.dao.MRByProjectID(c, projectID, since, until); err != nil {
return
}
for _, mr := range mrs {
for page := 1; ; page++ {
totalPage++
if notes, resp, err = s.gitlab.ListMRNotes(c, projectID, mr.MRIID, page); err != nil {
return
}
for _, note := range notes {
var (
notePosition string
notePositionByte []byte
)
if notePositionByte, err = json.Marshal(note.Position); err != nil {
return
}
notePosition = string(notePositionByte)
noteDB := &model.StatisticsNotes{
ProjectID: projectID,
ProjectName: projectInfo.Name,
MrIID: mr.MRIID,
NoteID: note.ID,
Body: note.Body,
Attachment: note.Attachment,
Title: note.Title,
FileName: note.FileName,
AuthorID: note.Author.ID,
AuthorName: note.Author.Name,
System: note.System,
ExpiresAt: note.ExpiresAt,
UpdatedAt: note.UpdatedAt,
CreatedAt: note.CreatedAt,
NoteableID: note.NoteableID,
NoteableType: note.NoteableType,
Position: notePosition,
Resolvable: note.Resolvable,
Resolved: note.Resolved,
ResolvedByID: note.ResolvedBy.ID,
ResolvedByName: note.ResolvedBy.Username,
NoteableIID: note.NoteableIID,
}
if err = s.SaveDatabaseNote(c, noteDB); err != nil {
log.Error("note Save Database err: projectID(%d), NoteID(%d)", projectID, note.ID)
err = nil
continue
}
totalNum++
}
if resp.NextPage == 0 {
break
}
}
}
return
}
// HandleNoteDBError ...
func (s *Service) HandleNoteDBError(c context.Context, noteDB *model.StatisticsNotes, errText string) (note *model.StatisticsNotes) {
if strings.Contains(errText, model.DatabaseErrorText) {
noteDB.Title = strconv.QuoteToASCII(noteDB.Title)
noteDB.Body = strconv.QuoteToASCII(noteDB.Body)
noteDB.Title = utils.Unicode2Chinese(noteDB.Title)
noteDB.Body = utils.Unicode2Chinese(noteDB.Body)
if len(noteDB.Body) > model.MessageMaxLen {
noteDB.Body = noteDB.Body[0 : model.MessageMaxLen-1]
}
} else if strings.Contains(errText, model.DatabaseMaxLenthErrorText) {
noteDB.Body = noteDB.Body[0 : model.MessageMaxLen-1]
}
return noteDB
}
// SaveDatabaseNote ...
func (s *Service) SaveDatabaseNote(c context.Context, noteDB *model.StatisticsNotes) (err error) {
var total int
if total, err = s.dao.HasNote(c, noteDB.ProjectID, noteDB.NoteID); err != nil {
log.Error("SaveDatabaseNote HasNote(%+v)", err)
return
}
// update found row
if total == 1 {
if err = s.dao.UpdateNote(c, noteDB.ProjectID, noteDB.NoteID, noteDB); err != nil {
noteDB = s.HandleNoteDBError(c, noteDB, err.Error())
if err = s.dao.UpdateNote(c, noteDB.ProjectID, noteDB.NoteID, noteDB); err != nil {
log.Error("SaveDatabaseNote UpdateNote(%+v)", err)
return
}
}
return
} else if total > 1 {
// found repeated row, this situation will not exist under normal
log.Warn("SaveDatabaseNote Note has more rows(%d)", total)
return
}
// insert row now
if err = s.dao.CreateNote(c, noteDB); err != nil {
noteDB = s.HandleNoteDBError(c, noteDB, err.Error())
if err = s.dao.CreateNote(c, noteDB); err != nil {
log.Error("SaveDatabaseNote CreateNote(%+v)", err)
return
}
}
return
}
/*-------------------------------------- sync emoji ----------------------------------------*/
// SyncProjectAwardEmoji ...
func (s *Service) SyncProjectAwardEmoji(c context.Context, projectID int) (totalPage, totalNum int, err error) {
var (
//syncAllTime = conf.Conf.Property.SyncData.SyncAllTime
syncAllTime = false
resp *gitlab.Response
projectInfo *model.ProjectInfo
mrs []*model.StatisticsMrs
awardEmojis []*gitlab.AwardEmoji
since *time.Time
until *time.Time
)
if projectInfo, err = s.dao.ProjectInfoByID(projectID); err != nil {
return
}
if !syncAllTime {
since, until = utils.CalSyncTime()
}
if mrs, err = s.dao.MRByProjectID(c, projectID, since, until); err != nil {
return
}
for _, mr := range mrs {
for page := 1; ; page++ {
totalPage++
if awardEmojis, resp, err = s.gitlab.ListMRAwardEmoji(projectID, mr.MRIID, page); err != nil {
return
}
for _, awardEmoji := range awardEmojis {
awardEmojiDB := &model.StatisticsMRAwardEmojis{
ProjectID: projectID,
ProjectName: projectInfo.Name,
MrIID: mr.MRIID,
AwardEmojiID: awardEmoji.ID,
Name: awardEmoji.Name,
UserID: awardEmoji.User.ID,
UserName: awardEmoji.User.Name,
CreatedAt: awardEmoji.CreatedAt,
UpdatedAt: awardEmoji.UpdatedAt,
AwardableID: awardEmoji.AwardableID,
AwardableType: awardEmoji.AwardableType,
}
if err = s.SaveDatabaseAwardEmoji(c, awardEmojiDB); err != nil {
log.Error("awardEmoji Save Database err: projectID(%d), AwardEmojiID(%d)", projectID, awardEmoji.ID)
err = nil
continue
}
totalNum++
}
if resp.NextPage == 0 {
break
}
}
}
return
}
// SaveDatabaseAwardEmoji ...
func (s *Service) SaveDatabaseAwardEmoji(c context.Context, awardEmojiDB *model.StatisticsMRAwardEmojis) (err error) {
var total int
if total, err = s.dao.HasMRAwardEmoji(c, awardEmojiDB.ProjectID, awardEmojiDB.MrIID, awardEmojiDB.AwardEmojiID); err != nil {
log.Error("SaveDatabaseAwardEmoji HasAwardEmoji(%+v)", err)
return
}
// update found row
if total == 1 {
if err = s.dao.UpdateMRAwardEmoji(c, awardEmojiDB.ProjectID, awardEmojiDB.MrIID, awardEmojiDB.AwardEmojiID, awardEmojiDB); err != nil {
log.Error("SaveDatabaseAwardEmoji UpdateAwardEmoji(%+v)", err)
}
return
} else if total > 1 {
// found repeated row, this situation will not exist under normal
log.Warn("SaveDatabaseAwardEmoji Note has more rows(%d)", total)
return
}
// insert row now
if err = s.dao.CreateMRAwardEmoji(c, awardEmojiDB); err != nil {
log.Error("SaveDatabaseAwardEmoji CreateAwardEmoji(%+v)", err)
return
}
return
}
/*-------------------------------------- sync Discussion ----------------------------------------*/
// SyncProjectDiscussion ...
func (s *Service) SyncProjectDiscussion(c context.Context, projectID int) (totalPage, totalNum int, err error) {
var (
//syncAllTime = conf.Conf.Property.SyncData.SyncAllTime
syncAllTime = false
resp *gitlab.Response
projectInfo *model.ProjectInfo
mrs []*model.StatisticsMrs
discussions []*gitlab.Discussion
since *time.Time
until *time.Time
)
if projectInfo, err = s.dao.ProjectInfoByID(projectID); err != nil {
return
}
if !syncAllTime {
since, until = utils.CalSyncTime()
}
if mrs, err = s.dao.MRByProjectID(c, projectID, since, until); err != nil {
return
}
for _, mr := range mrs {
for page := 1; ; page++ {
totalPage++
if discussions, resp, err = s.gitlab.ListMRDiscussions(projectID, mr.MRIID, page); err != nil {
return
}
for _, discussion := range discussions {
var (
notesArray []int
notesByte []byte
notes string
)
if discussion.Notes != nil {
for _, note := range discussion.Notes {
notesArray = append(notesArray, note.ID)
}
}
if notesByte, err = json.Marshal(notesArray); err != nil {
notes = model.JsonMarshalErrorText
} else {
notes = string(notesByte)
}
discussionDB := &model.StatisticsDiscussions{
ProjectID: projectID,
ProjectName: projectInfo.Name,
MrIID: mr.MRIID,
DiscussionID: discussion.ID,
IndividualNote: discussion.IndividualNote,
Notes: notes,
}
if err = s.SaveDatabaseDiscussion(c, discussionDB); err != nil {
log.Error("discussion Save Database err: projectID(%d), DiscussionID(%s)", projectID, discussion.ID)
err = nil
continue
}
totalNum++
}
if resp.NextPage == 0 {
break
}
}
}
return
}
// SaveDatabaseDiscussion ...
func (s *Service) SaveDatabaseDiscussion(c context.Context, discussionDB *model.StatisticsDiscussions) (err error) {
var total int
if total, err = s.dao.HasDiscussion(c, discussionDB.ProjectID, discussionDB.MrIID, discussionDB.DiscussionID); err != nil {
log.Error("SaveDatabaseDiscussion HasDiscussion(%+v)", err)
return
}
// update found row
if total == 1 {
if err = s.dao.UpdateDiscussion(c, discussionDB.ProjectID, discussionDB.MrIID, discussionDB.DiscussionID, discussionDB); err != nil {
log.Error("SaveDatabaseDiscussion UpdateDiscussion(%+v)", err)
}
return
} else if total > 1 {
// found repeated row, this situation will not exist under normal
log.Warn("SaveDatabaseDiscussion Note has more rows(%d)", total)
return
}
// insert row now
if err = s.dao.CreateDiscussion(c, discussionDB); err != nil {
log.Error("SaveDatabaseDiscussion CreateDiscussion(%+v)", err)
return
}
return
}

View File

@@ -0,0 +1,45 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["gitlab.go"],
importpath = "go-common/app/admin/ep/saga/service/gitlab",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/github.com/xanzy/go-gitlab: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"],
)
go_test(
name = "go_default_test",
srcs = ["gitlab_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/admin/ep/saga/conf:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)

View File

@@ -0,0 +1,239 @@
package gitlab
import (
"context"
"time"
"github.com/pkg/errors"
"github.com/xanzy/go-gitlab"
)
const (
_defaultPerPage = 20
_maxPerPage = 100
)
// Gitlab def
type Gitlab struct {
url string
token string
client *gitlab.Client
}
// New new gitlab structure
func New(url string, token string) (g *Gitlab) {
g = &Gitlab{
url: url,
token: token,
client: gitlab.NewClient(nil, token),
}
g.client.SetBaseURL(url)
return
}
// ListProjects list all project
func (g *Gitlab) ListProjects(page int) (projects []*gitlab.Project, err error) {
opt := &gitlab.ListProjectsOptions{}
opt.ListOptions.Page = page
opt.ListOptions.PerPage = _defaultPerPage
if projects, _, err = g.client.Projects.ListProjects(opt); err != nil {
err = errors.Wrapf(err, "ListProjects err(%+v)", err)
return
}
return
}
// ListProjectCommit ...
func (g *Gitlab) ListProjectCommit(projectID, page int, since, until *time.Time) (commits []*gitlab.Commit, resp *gitlab.Response, err error) {
var (
opt = &gitlab.ListCommitsOptions{}
all = true
)
opt.Page = page
opt.PerPage = _maxPerPage
opt.All = &all
if since != nil {
opt.Since = since
}
if until != nil {
opt.Until = until
}
if commits, resp, err = g.client.Commits.ListCommits(projectID, opt); err != nil {
err = errors.Wrapf(err, "ListCommits projectId(%d), err(%+v)", projectID, err)
return
}
return
}
// ListProjectMergeRequests list all MR
func (g *Gitlab) ListProjectMergeRequests(projectID int, since, until *time.Time, page int) (mrs []*gitlab.MergeRequest, resp *gitlab.Response, err error) {
opt := &gitlab.ListProjectMergeRequestsOptions{}
opt.UpdatedAfter = since
opt.UpdatedBefore = until
if page != -1 {
opt.PerPage = _defaultPerPage
opt.Page = page
}
if mrs, resp, err = g.client.MergeRequests.ListProjectMergeRequests(projectID, opt); err != nil {
err = errors.Wrapf(err, "ListProjectMergeRequests projectId(%d), err(%+v)", projectID, err)
return
}
return
}
// ListProjectPipelines list all pipelines
func (g *Gitlab) ListProjectPipelines(page, projectID int, status gitlab.BuildStateValue) (pipelineList gitlab.PipelineList, resp *gitlab.Response, err error) {
opt := &gitlab.ListProjectPipelinesOptions{}
opt.ListOptions.Page = page
if status != "" {
opt.Status = gitlab.BuildState(status)
}
if pipelineList, resp, err = g.client.Pipelines.ListProjectPipelines(projectID, opt); err != nil {
err = errors.Wrapf(err, "ListProjectPipelines projectId(%d), err(%+v)", projectID, err)
return
}
return
}
// ListPipelineJobs list all pipeline jobs
func (g *Gitlab) ListPipelineJobs(opt *gitlab.ListJobsOptions, projectID, pipelineID int) (jobList []*gitlab.Job, resp *gitlab.Response, err error) {
if jobList, resp, err = g.client.Jobs.ListPipelineJobs(projectID, pipelineID, opt); err != nil {
err = errors.Wrapf(err, "ListPipelineJobs projectId(%d), pipelineId(%d), err(%+v)", projectID, pipelineID, err)
return
}
return
}
// GetPipeline get a pipeline info
func (g *Gitlab) GetPipeline(projectID, pipelineID int) (pipeline *gitlab.Pipeline, resp *gitlab.Response, err error) {
if pipeline, resp, err = g.client.Pipelines.GetPipeline(projectID, pipelineID); err != nil {
err = errors.Wrapf(err, "GetPipeline projectId(%d), err(%+v)", projectID, err)
return
}
return
}
// ListProjectBranch ...
func (g *Gitlab) ListProjectBranch(projectID, page int) (branches []*gitlab.Branch, resp *gitlab.Response, err error) {
var opt = &gitlab.ListBranchesOptions{Page: page, PerPage: 20}
if branches, resp, err = g.client.Branches.ListBranches(projectID, opt); err != nil {
err = errors.Wrapf(err, "ListBranches projectId(%d), err(%+v)", projectID, err)
return
}
return
}
// ListMRNotes get notes of the MR
func (g *Gitlab) ListMRNotes(c context.Context, projectID, mrID, page int) (notes []*gitlab.Note, resp *gitlab.Response, err error) {
var opt = &gitlab.ListMergeRequestNotesOptions{Page: page, PerPage: 20}
if notes, resp, err = g.client.Notes.ListMergeRequestNotes(projectID, mrID, opt); err != nil {
err = errors.Wrapf(err, "ListMergeRequestNotes projectId(%d), mrId(%d), err(%+v)", projectID, mrID, err)
return
}
return
}
// MergeBase 获取两个分支最近的合并commit
func (g *Gitlab) MergeBase(c context.Context, projectID int, refs []string) (commit *gitlab.Commit, resp *gitlab.Response, err error) {
var opt = &gitlab.MergeBaseOptions{Ref: refs}
if commit, resp, err = g.client.Repositories.MergeBase(projectID, opt); err != nil {
err = errors.Wrapf(err, "MergeBase projectId(%d), refs(%v), err(%+v)", projectID, refs, err)
return
}
return
}
// ListMRAwardEmoji ...
func (g *Gitlab) ListMRAwardEmoji(projectID, mrIID, page int) (emojis []*gitlab.AwardEmoji, resp *gitlab.Response, err error) {
var opt = &gitlab.ListAwardEmojiOptions{Page: page, PerPage: _defaultPerPage}
if emojis, resp, err = g.client.AwardEmoji.ListMergeRequestAwardEmoji(projectID, mrIID, opt); err != nil {
err = errors.Wrapf(err, "ListMergeRequestAwardEmoji projectId(%d), mrIID(%d), err(%+v)", projectID, mrIID, err)
return
}
return
}
//ListMRDiscussions ...
func (g *Gitlab) ListMRDiscussions(projectID, mrIID, page int) (result []*gitlab.Discussion, resp *gitlab.Response, err error) {
var opt = &gitlab.ListMergeRequestDiscussionsOptions{Page: page, PerPage: _defaultPerPage}
if result, resp, err = g.client.Discussions.ListMergeRequestDiscussions(projectID, mrIID, opt); err != nil {
err = errors.Wrapf(err, "ListMergeRequestDiscussions projectId(%d), mrIID(%d), err(%+v)", projectID, mrIID, err)
return
}
return
}
// GetMergeRequestDiff ...
func (g *Gitlab) GetMergeRequestDiff(projectID, mrID int) (mr *gitlab.MergeRequest, resp *gitlab.Response, err error) {
if mr, resp, err = g.client.MergeRequests.GetMergeRequestChanges(projectID, mrID); err != nil {
err = errors.Wrapf(err, "GetMergeRequestChanges projectId(%d), err(%+v)", projectID, err)
return
}
return
}
// ListProjectJobs get jobs of the project
func (g *Gitlab) ListProjectJobs(projID, page int) (jobs []gitlab.Job, resp *gitlab.Response, err error) {
var opt = &gitlab.ListJobsOptions{}
opt.Page = page
// perPage max is 100
opt.PerPage = _maxPerPage
if jobs, resp, err = g.client.Jobs.ListProjectJobs(projID, opt); err != nil {
err = errors.Wrapf(err, "ListProjectJobs projectId(%d), err(%+v)", projID, err)
return
}
return
}
// ListProjectMembers ...
func (g *Gitlab) ListProjectMembers(projectID, page int) (members []*gitlab.ProjectMember, resp *gitlab.Response, err error) {
opt := &gitlab.ListProjectMembersOptions{}
opt.Page = page
opt.PerPage = _defaultPerPage
if members, resp, err = g.client.ProjectMembers.ListProjectMembers(projectID, opt); err != nil {
err = errors.Wrapf(err, "ListProjectMembers projectId(%d), err(%+v)", projectID, err)
return
}
return
}
// ListProjectIssues ...
func (g *Gitlab) ListProjectIssues(projID, page int, since, until *time.Time) (issues []*gitlab.Issue, resp *gitlab.Response, err error) {
var opt = &gitlab.ListProjectIssuesOptions{}
opt.Page = page
// perPage max is 100
opt.PerPage = _maxPerPage
if since != nil {
opt.CreatedBefore = until
}
if until != nil {
opt.CreatedAfter = since
}
if issues, resp, err = g.client.Issues.ListProjectIssues(projID, opt); err != nil {
err = errors.Wrapf(err, "ListProjectIssues projectId(%d), err(%+v)", projID, err)
return
}
return
}
// ListProjectRunners ...
func (g *Gitlab) ListProjectRunners(projID, page int) (result []*gitlab.Runner, resp *gitlab.Response, err error) {
var opt = &gitlab.ListProjectRunnersOptions{}
opt.ListOptions.Page = page
opt.PerPage = _defaultPerPage
if result, resp, err = g.client.Runners.ListProjectRunners(projID, opt); err != nil {
err = errors.Wrapf(err, "ListProjectRunners projectId(%d), err(%+v)", projID, err)
return
}
return
}

View File

@@ -0,0 +1,113 @@
package gitlab
import (
"flag"
"os"
"testing"
"time"
"go-common/app/admin/ep/saga/conf"
. "github.com/smartystreets/goconvey/convey"
)
var (
g *Gitlab
)
// TestMain ...
func TestMain(m *testing.M) {
flag.Set("conf", "../../cmd/saga-admin-test.toml")
var err error
if err = conf.Init(); err != nil {
panic(err)
}
g = New(conf.Conf.Property.Gitlab.API, conf.Conf.Property.Gitlab.Token)
os.Exit(m.Run())
}
// TestListProjects ...
func TestListProjects(t *testing.T) {
Convey("ListProjects", t, func() {
projects, err := g.ListProjects(1)
So(err, ShouldBeNil)
So(len(projects), ShouldBeGreaterThan, 1)
})
}
func TestListProjectPipelines(t *testing.T) {
Convey("listProjectPipelines", t, func() {
_, _, err := g.ListProjectPipelines(1, 682, "")
So(err, ShouldBeNil)
})
}
func TestGetPipeline(t *testing.T) {
Convey("GetPipeline", t, func() {
_, _, err := g.GetPipeline(682, 166011)
So(err, ShouldBeNil)
})
}
func TestListProjectJobs(t *testing.T) {
Convey("ListJobs", t, func() {
jobs, resp, err := g.ListProjectJobs(5822, 1)
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(len(jobs), ShouldBeGreaterThan, 1)
})
}
func TestGitlab_ListProjectMergeRequests(t *testing.T) {
Convey("ListProjectMergeRequest", t, func() {
var (
project = 682
until = time.Now()
since = until.AddDate(0, -1, 0)
)
mrs, resp, err := g.ListProjectMergeRequests(project, &since, &until, -1)
So(len(mrs), ShouldBeGreaterThan, 1)
So(resp, ShouldNotBeNil)
So(err, ShouldBeNil)
})
}
func TestGitlab_ListProjectBranch(t *testing.T) {
Convey("ListProjectBranch", t, func() {
var (
project = 682
page = 1
)
branches, resp, err := g.ListProjectBranch(project, page)
So(err, ShouldBeNil)
So(len(branches), ShouldBeGreaterThan, 0)
So(resp, ShouldNotBeNil)
})
}
func TestGitlab_ListProjectCommit(t *testing.T) {
Convey("List Project branch commit", t, func() {
var (
project = 682
page = 1
)
commits, resp, err := g.ListProjectCommit(project, page, nil, nil)
So(err, ShouldBeNil)
So(commits, ShouldNotBeNil)
So(resp.StatusCode, ShouldEqual, 200)
})
}
func TestGitlab_ListProjectRunners(t *testing.T) {
Convey("test list project runners", t, func() {
var (
project = 4928
page = 1
)
runners, resp, err := g.ListProjectRunners(project, page)
So(err, ShouldBeNil)
So(resp.StatusCode, ShouldEqual, 200)
So(runners, ShouldNotBeNil)
})
}

View File

@@ -0,0 +1,485 @@
package service
import (
"context"
"encoding/json"
"fmt"
"time"
"go-common/app/admin/ep/saga/model"
"go-common/app/admin/ep/saga/service/utils"
"go-common/library/cache/redis"
"go-common/library/log"
"github.com/xanzy/go-gitlab"
)
// QueryProjectJob ...
func (s *Service) QueryProjectJob(c context.Context, req *model.ProjectJobRequest) (resp *model.ProjectJobResp, err error) {
var (
layout = "2006-01-02"
queryCacheKey string
jobs []*model.ProjectJob
since time.Time
util time.Time
)
resp = &model.ProjectJobResp{ProjectID: req.ProjectID, QueryDescription: "最近一月的Jobs日常", State: req.Scope, DataInfo: []*model.DateJobInfo{}}
year, month, day := time.Now().Date()
util = time.Date(year, month, day-1, 0, 0, 0, 0, time.Local)
since = util.AddDate(0, -1, 0)
//query from redis first
queryCacheKey = fmt.Sprintf("saga_admin_job_%d_%s_%s_%s_%d", req.ProjectID, req.Branch, req.Scope, req.Machine, req.StatisticsType)
if err = s.dao.ItemRedis(c, queryCacheKey, &resp); err != redis.ErrNil {
return
}
if resp.TotalItem, jobs, err = s.queryProjectJobByTime(c, req.ProjectID, since, util); err != nil {
return
}
//init map key
pendingTime := make(map[string][]float64)
runningTime := make(map[string][]float64)
for i := 1; ; i++ {
day := since.AddDate(0, 0, i)
if day.After(util) {
break
}
dayStr := day.Format(layout)
pendingTime[dayStr] = []float64{}
runningTime[dayStr] = []float64{}
resp.DataInfo = append(resp.DataInfo, &model.DateJobInfo{Date: dayStr, SlowestPendingJob: []*model.ProjectJob{}})
}
//根据查询条件进行过滤
newJobs := jobs[:0]
if req.Branch != "" {
for _, job := range jobs {
if job.Branch == req.Branch {
newJobs = append(newJobs, job)
}
}
jobs = newJobs
newJobs = jobs[:0]
}
if req.User != "" {
for _, job := range jobs {
if job.User == req.User {
newJobs = append(newJobs, job)
}
}
jobs = newJobs
newJobs = jobs[:0]
}
if req.Machine != "" {
for _, job := range jobs {
if job.Machine == req.Machine {
newJobs = append(newJobs, job)
}
}
jobs = newJobs
}
//统计pending running 时间
for _, job := range jobs {
var (
jobInfo *model.DateJobInfo
)
jobDate := job.CreatedAt.Format(layout)
for _, j := range resp.DataInfo {
if j.Date == jobDate {
jobInfo = j
break
}
}
jobInfo.JobTotal++
if job.Status == req.Scope {
jobInfo.StatusNum++
if job.StartedAt != nil && job.CreatedAt != nil {
pending := job.StartedAt.Sub(*job.CreatedAt).Seconds()
pendingTime[jobDate] = append(pendingTime[jobDate], pending)
if pending >= 300 {
jobInfo.SlowestPendingJob = append(jobInfo.SlowestPendingJob, job)
}
}
if job.Status == "success" {
running := job.FinishedAt.Sub(*job.StartedAt).Seconds()
runningTime[jobDate] = append(runningTime[jobDate], running)
}
}
}
for k, v := range runningTime {
var (
jobInfo *model.DateJobInfo
)
for _, j := range resp.DataInfo {
if j.Date == k {
jobInfo = j
break
}
}
jobInfo.PendingTime = utils.CalAverageTime(req.StatisticsType, v)
jobInfo.RunningTime = utils.CalAverageTime(req.StatisticsType, pendingTime[k])
}
// set data to redis
err = s.dao.SetItemRedis(c, queryCacheKey, resp, model.ExpiredOneDay)
return
}
// queryProjectJobByTime ...
func (s *Service) queryProjectJobByTime(c context.Context, projectID int, since, util time.Time) (count int, result []*model.ProjectJob, err error) {
var (
resp *gitlab.Response
jobs []gitlab.Job
overTime bool
)
if _, resp, err = s.gitlab.ListProjectJobs(projectID, 1); err != nil {
return
}
if resp.TotalItems <= 0 {
return
}
for page := 1; ; page++ {
if jobs, resp, err = s.gitlab.ListProjectJobs(projectID, page); err != nil {
return
}
for _, job := range jobs {
if job.CreatedAt == nil {
continue
}
if job.CreatedAt.After(since) && job.CreatedAt.Before(util) {
count++
jobInfo := &model.ProjectJob{
Status: job.Status,
Branch: job.Ref,
Machine: job.Runner.Description,
User: job.User.Name,
CreatedAt: job.CreatedAt,
StartedAt: job.StartedAt,
FinishedAt: job.FinishedAt}
result = append(result, jobInfo)
}
if job.CreatedAt.Before(since) {
overTime = true
}
}
if overTime {
break
}
if resp.NextPage == 0 {
break
}
}
return
}
// QueryProjectJobNew ...
func (s *Service) QueryProjectJobNew(c context.Context, req *model.ProjectJobRequest) (resp *model.ProjectJobResp, err error) {
var (
layout = "2006-01-02"
fmtLayout = `%d-%d-%d 00:00:00`
jobs []*model.StatisticsJobs
)
resp = &model.ProjectJobResp{ProjectID: req.ProjectID, QueryDescription: "最近一月的Jobs日常", State: req.Scope, DataInfo: []*model.DateJobInfo{}}
year, month, day := time.Now().Date()
until := time.Date(year, month, day-1, 0, 0, 0, 0, time.Local)
since := until.AddDate(0, -1, 0)
sinceStr := fmt.Sprintf(fmtLayout, since.Year(), since.Month(), since.Day())
untilStr := fmt.Sprintf(fmtLayout, until.Year(), until.Month(), until.Day())
if resp.TotalItem, jobs, err = s.dao.QueryJobsByTime(req.ProjectID, req, sinceStr, untilStr); err != nil {
return
}
//init map key
pendingTime := make(map[string][]float64)
runningTime := make(map[string][]float64)
for i := 1; ; i++ {
day := since.AddDate(0, 0, i)
if day.After(until) {
break
}
dayStr := day.Format(layout)
pendingTime[dayStr] = []float64{}
runningTime[dayStr] = []float64{}
resp.DataInfo = append(resp.DataInfo, &model.DateJobInfo{Date: dayStr, SlowestPendingJob: []*model.ProjectJob{}})
}
//统计pending running 时间
for _, job := range jobs {
var (
jobInfo *model.DateJobInfo
)
jobDate := job.CreatedAt.Format(layout)
for _, j := range resp.DataInfo {
if j.Date == jobDate {
jobInfo = j
break
}
}
if jobInfo == nil {
continue
}
jobInfo.JobTotal++
if job.Status == req.Scope {
jobInfo.StatusNum++
if job.StartedAt != nil && job.CreatedAt != nil {
pending := job.StartedAt.Sub(*job.CreatedAt).Seconds()
pendingTime[jobDate] = append(pendingTime[jobDate], pending)
if pending >= 300 {
jo := &model.ProjectJob{
Status: job.Status,
User: job.UserName,
Branch: job.Ref,
Machine: job.RunnerDescription,
CreatedAt: job.CreatedAt,
StartedAt: job.StartedAt,
FinishedAt: job.FinishedAt,
}
jobInfo.SlowestPendingJob = append(jobInfo.SlowestPendingJob, jo)
}
}
if job.Status == "success" {
running := job.FinishedAt.Sub(*job.StartedAt).Seconds()
runningTime[jobDate] = append(runningTime[jobDate], running)
}
}
}
for k, v := range runningTime {
var (
jobInfo *model.DateJobInfo
)
for _, j := range resp.DataInfo {
if j.Date == k {
jobInfo = j
break
}
}
jobInfo.PendingTime = utils.CalAverageTime(req.StatisticsType, v)
jobInfo.RunningTime = utils.CalAverageTime(req.StatisticsType, pendingTime[k])
}
return
}
/*-------------------------------------- sync job ----------------------------------------*/
// SyncProjectJobs ...
func (s *Service) SyncProjectJobs(projectID int) (result *model.SyncResult, err error) {
var (
//syncAllTime = conf.Conf.Property.SyncData.SyncAllTime
syncAllTime = false
since *time.Time
until *time.Time
projectInfo *model.ProjectInfo
)
if projectInfo, err = s.dao.ProjectInfoByID(projectID); err != nil {
return
}
if !syncAllTime {
since, until = utils.CalSyncTime()
log.Info("sync project(%d) job time since: %v, until: %v", projectID, since, until)
if result, err = s.SyncProjectJobsByTime(projectID, projectInfo.Name, *since, *until); err != nil {
return
}
} else {
if result, err = s.SyncProjectJobsNormal(projectID, projectInfo.Name); err != nil {
return
}
}
return
}
// SyncProjectJobsNormal ...
func (s *Service) SyncProjectJobsNormal(projectID int, projectName string) (result *model.SyncResult, err error) {
var (
jobs []gitlab.Job
resp *gitlab.Response
)
result = &model.SyncResult{}
for page := 1; ; page++ {
result.TotalPage++
if jobs, resp, err = s.gitlab.ListProjectJobs(projectID, page); err != nil {
return
}
for _, job := range jobs {
if err = s.structureDatabasejob(projectID, projectName, job); err != nil {
log.Error("job Save Database err: projectID(%d), JobID(%d)", projectID, job.ID)
err = nil
errData := &model.FailData{
ChildID: job.ID,
}
result.FailData = append(result.FailData, errData)
continue
}
result.TotalNum++
}
if resp.NextPage == 0 {
break
}
}
return
}
// SyncProjectJobsByTime ...
func (s *Service) SyncProjectJobsByTime(projectID int, projectName string, since, until time.Time) (result *model.SyncResult, err error) {
var (
jobs []gitlab.Job
resp *gitlab.Response
startQuery bool
)
result = &model.SyncResult{}
if _, resp, err = s.gitlab.ListProjectJobs(projectID, 1); err != nil {
return
}
page := 1
for page <= resp.TotalPages {
result.TotalPage++
if !startQuery {
if jobs, _, err = s.gitlab.ListProjectJobs(projectID, page); err != nil {
return
}
if page == 1 && len(jobs) <= 0 {
return
}
if jobs[0].CreatedAt.After(until) {
page++
continue
} else {
startQuery = true
page--
continue
}
}
if jobs, _, err = s.gitlab.ListProjectJobs(projectID, page); err != nil {
return
}
for _, job := range jobs {
createTime := job.CreatedAt
if createTime.After(since) && createTime.Before(until) {
if err = s.structureDatabasejob(projectID, projectName, job); err != nil {
log.Error("job Save Database err: projectID(%d), JobID(%d)", projectID, job.ID)
err = nil
errData := &model.FailData{
ChildID: job.ID,
}
result.FailData = append(result.FailData, errData)
continue
}
result.TotalNum++
}
if createTime.Before(since) {
return
}
}
page++
}
return
}
// structureDatabasejob ...
func (s *Service) structureDatabasejob(projectID int, projectName string, job gitlab.Job) (err error) {
var (
jobArtifactsFile string
jobCommitID string
)
jobArtifactsFileByte, _ := json.Marshal(job.ArtifactsFile)
jobArtifactsFile = string(jobArtifactsFileByte)
if job.Commit != nil {
jobCommitID = job.Commit.ID
}
jobDB := &model.StatisticsJobs{
ProjectID: projectID,
ProjectName: projectName,
CommitID: jobCommitID,
CreatedAt: job.CreatedAt,
Coverage: job.Coverage,
ArtifactsFile: jobArtifactsFile,
FinishedAt: job.FinishedAt,
JobID: job.ID,
Name: job.Name,
Ref: job.Ref,
RunnerID: job.Runner.ID,
RunnerDescription: job.Runner.Description,
Stage: job.Stage,
StartedAt: job.StartedAt,
Status: job.Status,
Tag: job.Tag,
UserID: job.User.ID,
UserName: job.User.Name,
WebURL: job.WebURL,
}
return s.SaveDatabasejob(jobDB)
}
// SaveDatabasejob ...
func (s *Service) SaveDatabasejob(jobDB *model.StatisticsJobs) (err error) {
var total int
if total, err = s.dao.HasJob(jobDB.ProjectID, jobDB.JobID); err != nil {
log.Error("SaveDatabaseJob HasJob(%+v)", err)
return
}
// found only one, so update
if total == 1 {
if err = s.dao.UpdateJob(jobDB.ProjectID, jobDB.JobID, jobDB); err != nil {
log.Error("SaveDatabaseJob UpdateJob(%+v)", err)
return
}
return
} else if total > 1 {
// found repeated row, this situation will not exist under normal
log.Warn("SaveDatabasejob job has more rows(%d)", total)
return
}
// insert row now
if err = s.dao.CreateJob(jobDB); err != nil {
log.Error("SaveDatabaseJob CreateJob(%+v)", err)
return
}
return
}

View File

@@ -0,0 +1,50 @@
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"mail.go",
"tpl.go",
],
importpath = "go-common/app/admin/ep/saga/service/mail",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/admin/ep/saga/conf:go_default_library",
"//app/admin/ep/saga/model:go_default_library",
"//library/log:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/gopkg.in/gomail.v2:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["mail_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/admin/ep/saga/conf:go_default_library",
"//app/admin/ep/saga/model:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey: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,112 @@
package mail
import (
"bytes"
"fmt"
"text/template"
"go-common/app/admin/ep/saga/conf"
"go-common/app/admin/ep/saga/model"
"go-common/library/log"
"github.com/pkg/errors"
gomail "gopkg.in/gomail.v2"
)
// SendMail2 ...
func SendMail2(addr *model.MailAddress, subject string, data string) (err error) {
var (
msg = gomail.NewMessage()
)
msg.SetAddressHeader("From", conf.Conf.Property.Mail.Address, conf.Conf.Property.Mail.Name)
msg.SetHeader("To", msg.FormatAddress(addr.Address, addr.Name))
msg.SetHeader("Subject", subject)
msg.SetBody("text/plain", data)
d := gomail.NewDialer(
conf.Conf.Property.Mail.Host,
conf.Conf.Property.Mail.Port,
conf.Conf.Property.Mail.Address,
conf.Conf.Property.Mail.Pwd,
)
if err = d.DialAndSend(msg); err != nil {
err = errors.WithMessage(err, fmt.Sprintf("Send mail (%+v,%s,%s) failed", addr, subject, data))
return
}
log.Info("Send mail (%+v,%s,%s) success", addr, subject, data)
return
}
// SendMail send mail
func SendMail(m *model.Mail, data *model.MailData) (err error) {
var (
toUsers []string
msg = gomail.NewMessage()
buf = &bytes.Buffer{}
)
msg.SetAddressHeader("From", conf.Conf.Property.Mail.Address, conf.Conf.Property.Mail.Name) // 发件人
for _, ads := range m.ToAddress {
toUsers = append(toUsers, msg.FormatAddress(ads.Address, ads.Name))
}
t := template.New("MR Mail")
if t, err = t.Parse(mailTPL); err != nil {
log.Error("tpl.Parse(%s) error(%+v)", mailTPL, errors.WithStack(err))
return
}
err = t.Execute(buf, data)
if err != nil {
log.Error("t.Execute error(%+v)", errors.WithStack(err))
return
}
msg.SetHeader("To", toUsers...)
msg.SetHeader("Subject", m.Subject) // 主题
msg.SetBody("text/html", buf.String()) // 正文
d := gomail.NewDialer(
conf.Conf.Property.Mail.Host,
conf.Conf.Property.Mail.Port,
conf.Conf.Property.Mail.Address,
conf.Conf.Property.Mail.Pwd,
)
if err = d.DialAndSend(msg); err != nil {
log.Error("Send mail Fail(%v) diff(%s)", msg, err)
return
}
return
}
// SendMail3 SendMail all parameter
func SendMail3(from string, sender string, senderPwd string, m *model.Mail, data *model.MailData) (err error) {
var (
toUsers []string
msg = gomail.NewMessage()
buf = &bytes.Buffer{}
)
msg.SetAddressHeader("From", from, sender) // 发件人
for _, ads := range m.ToAddress {
toUsers = append(toUsers, msg.FormatAddress(ads.Address, ads.Name))
}
t := template.New("MR Mail")
if t, err = t.Parse(mailTPL3); err != nil {
log.Error("tpl.Parse(%s) error(%+v)", mailTPL3, errors.WithStack(err))
return
}
err = t.Execute(buf, data)
if err != nil {
log.Error("t.Execute error(%+v)", errors.WithStack(err))
return
}
msg.SetHeader("To", toUsers...)
msg.SetHeader("Subject", m.Subject) // 主题
msg.SetBody("text/html", buf.String()) // 正文
d := gomail.NewDialer(
"smtp.exmail.qq.com",
465,
from,
senderPwd,
)
if err = d.DialAndSend(msg); err != nil {
log.Error("Send mail Fail(%v) diff(%s)", msg, err)
return
}
return
}

View File

@@ -0,0 +1,46 @@
package mail
import (
"flag"
"fmt"
"testing"
"go-common/app/admin/ep/saga/conf"
"go-common/app/admin/ep/saga/model"
. "github.com/smartystreets/goconvey/convey"
)
func init() {
var err error
flag.Set("conf", "../../cmd/saga-admin-test.toml")
if err = conf.Init(); err != nil {
panic(err)
}
}
// go test -test.v -test.run TestMail
func TestMail(t *testing.T) {
Convey("Test mail", t, func() {
m := &model.Mail{
ToAddress: []*model.MailAddress{{Name: "baihai", Address: "yubaihai@bilibili.com"},
{Name: "muyan", Address: "muyang@bilibili.com"}},
Subject: fmt.Sprintf("【Sage 提醒】%s项目发生Merge Request事件", "kk"),
}
mergeOut := " Merge made by the 'recursive' strategy.\n" +
"tools/saga/CHANGELOG.md | 4 ++++\n" +
"business/interface/app-show/service/rank/rank.go | 28 +++++++++++------------\n" +
"business/interface/app-show/service/show/cache.go | 6 ++---\n" +
"3 files changed, 21 insertions(+), 17 deletions(-)"
err := SendMail(m, &model.MailData{
UserName: "baihai",
SourceBranch: "featre_answer",
TargetBranch: "master",
Title: "修改变量A",
Description: "内容就是",
URL: "http://www.baidu.com",
Info: mergeOut,
})
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,130 @@
package mail
var (
mailTPL = `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>saga page</title>
</head>
<body>
<form action="" method="post" class="basic-grey" style="margin-left:auto;
margin-right:auto;
max-width: 500px;
background: #F7F7F7;
padding: 25px 15px 25px 10px;
font: 12px Georgia, 'Times New Roman', Times, serif;
color: #888;
text-shadow: 1px 1px 1px #FFF;
border:1px solid #E4E4E4;">
<h1 style="font-size: 25px;
padding: 0px 0px 10px 40px;
display: block;
border-bottom:1px solid #E4E4E4;
margin: -10px -15px 30px -10px;;
color: #888;">
Saga
<span style="display: block;font-size: 11px;">
Merge Request 事件通知</span>
</h1>
<label style="display: block;margin: 0px;">
<span style="float: left;
width:100%;
text-align: left;
padding-right: 10px;
padding-left: 30px;
margin-top: 10px;
color: #888;">
申请人 : {{.UserName}}
<br />
来源分支 : {{.SourceBranch}}
<br />
目标分支 : {{.TargetBranch}}
<br />
修改标题 : {{.Title}}
<br />
修改说明 : {{.Description}}
<br />
<a href="{{.URL}}">点击查看..</a>
<br />
<br />
<br />
<h3>额外信息: </h3>
{{.Info}}
<br />
<br />
<br />
</span>
</label>
</form>
</body>
</html>`
mailTPL3 = `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>saga page</title>
</head>
<body>
<form action="" method="post" class="basic-grey" style="
margin-right:auto;
max-width: 500px;
font: 12px Georgia, 'Times New Roman', Times, serif;
color: #888;
text-shadow: 1px 1px 1px #FFF;">
<h1 style="font-size: 25px;
padding: 10px 0px 10px 40px;
display: block;
border-bottom:1px solid #E4E4E4;
margin: -10px -15px 5px -10px;;
color: #03A9F4;">
Saga
<span style="display: block;font-size: 11px;">
事件通知</span>
</h1>
</form>
<label style="display: block;margin: 0px;">
<span style="float: left;
width:100%;
text-align: left;
padding-right: 10px;
padding-left: 30px;
margin-top: 10px;
color: #888;">
执行状态 :
<font class="{{.PipelineStatus}}" >
{{.PipeStatus}}
</font>
<br />
Pipeline信息:
<font class="{{.PipelineStatus}}" >
<a href="{{.URL}}">{{.URL}}</a>
</font>
<br />
来源分支 : {{.SourceBranch}}
<br />
修改说明 : {{.Description}}
<br />
额外信息: {{.Info}}
<br />
<br />
<br />
</span>
</label>
</body>
<style type="text/css">
.failed {
color: #f21303;
}
.failed a{
color: #f21303;
}
.success {
color: #1aaa55;
}
.success a{
color: #1aaa55;
}
</style>
</html>`
)

View File

@@ -0,0 +1,110 @@
package service
import (
"context"
"go-common/app/admin/ep/saga/model"
"go-common/library/log"
"github.com/xanzy/go-gitlab"
)
// QueryProjectMembers ...
func (s *Service) QueryProjectMembers(c context.Context, req *model.ProjectDataReq) (resp []*gitlab.ProjectMember, err error) {
var (
members []*gitlab.ProjectMember
response *gitlab.Response
memberItem []*gitlab.ProjectMember
)
if members, response, err = s.gitlab.ListProjectMembers(req.ProjectID, 1); err != nil {
return
}
page := 2
for page <= response.TotalPages {
memberItem, _, err = s.gitlab.ListProjectMembers(req.ProjectID, page)
members = append(members, memberItem...)
page++
}
resp = members
return
}
/*-------------------------------------- sync member ----------------------------------------*/
// SyncProjectMember ...
func (s *Service) SyncProjectMember(c context.Context, projectID int) (totalPage, totalNum int, err error) {
var (
resp *gitlab.Response
members []*gitlab.ProjectMember
projectInfo *model.ProjectInfo
)
if projectInfo, err = s.dao.ProjectInfoByID(projectID); err != nil {
return
}
for page := 1; ; page++ {
totalPage++
if members, resp, err = s.gitlab.ListProjectMembers(projectID, page); err != nil {
return
}
for _, member := range members {
memberDB := &model.StatisticsMembers{
ProjectID: projectID,
ProjectName: projectInfo.Name,
MemberID: member.ID,
Username: member.Username,
Email: member.Email,
Name: member.Name,
State: member.State,
CreatedAt: member.CreatedAt,
AccessLevel: int(member.AccessLevel),
}
if err = s.SaveDatabaseMember(c, memberDB); err != nil {
log.Error("member Save Database err: projectID(%d), MemberID(%d)", projectID, member.ID)
err = nil
continue
}
totalNum++
}
if resp.NextPage == 0 {
break
}
}
return
}
// SaveDatabaseMember ...
func (s *Service) SaveDatabaseMember(c context.Context, memberDB *model.StatisticsMembers) (err error) {
var total int
if total, err = s.dao.HasMember(c, memberDB.ProjectID, memberDB.MemberID); err != nil {
log.Error("SaveDatabaseMember HasMember(%+v)", err)
return
}
// update found row
if total == 1 {
if err = s.dao.UpdateMember(c, memberDB.ProjectID, memberDB.MemberID, memberDB); err != nil {
log.Error("SaveDatabaseMember UpdateMember(%+v)", err)
}
return
} else if total > 1 {
// found repeated row, this situation will not exist under normal
log.Warn("SaveDatabaseMember Note has more rows(%d)", total)
return
}
// insert row now
if err = s.dao.CreateMember(c, memberDB); err != nil {
log.Error("SaveDatabaseMember CreateMember(%+v)", err)
return
}
return
}

View File

@@ -0,0 +1,590 @@
package service
import (
"context"
"encoding/json"
"fmt"
"regexp"
"strings"
"time"
"go-common/app/admin/ep/saga/model"
"go-common/app/admin/ep/saga/service/utils"
"go-common/library/log"
"github.com/xanzy/go-gitlab"
)
const _specialMrID = 10199
// QueryProjectMr query project commit info according to project id.
func (s *Service) QueryProjectMr(c context.Context, req *model.ProjectDataReq) (resp *model.ProjectDataResp, err error) {
if resp, err = s.QueryProject(c, model.ObjectMR, req); err != nil {
return
}
return
}
// QueryTeamMr query team commit info according to department and business
func (s *Service) QueryTeamMr(c context.Context, req *model.TeamDataRequest) (resp *model.TeamDataResp, err error) {
if resp, err = s.QueryTeam(c, model.ObjectMR, req); err != nil {
return
}
return
}
// QueryProjectMrReport query mr review
func (s *Service) QueryProjectMrReport(c context.Context, req *model.ProjectMrReportReq) (resp *model.ProjectMrReportResp, err error) {
var (
info []*model.MrInfo
changeAdd int
changeDel int
mrCount int
stateCount int
discussionCount int
discussionResolved int
mrTime int
spentTime time.Duration
avarageTime time.Duration
reviewers []string
reviews []string
reviewChangeAdd int
reviewChangeDel int
reviewTime int
reviewTotalTime time.Duration
)
if info, err = s.QueryAllMergeRequestInfo(c, req.ProjectID); err != nil {
return
}
for _, i := range info {
for _, r := range i.Reviewers {
if r.Name == req.Member {
reviews = append(reviews, i.Author)
reviewChangeAdd += i.ChangeAdd
reviewChangeDel += i.ChangeDel
reviewTime += i.SpentTime
}
}
if i.Author == req.Member {
mrCount++
if i.State == model.StatusMerged {
stateCount++
mrTime += i.SpentTime
changeAdd += i.ChangeAdd
changeDel += i.ChangeDel
discussionCount += i.TotalDiscussion
discussionResolved += i.SolvedDiscussion
for _, r := range i.Reviewers {
reviewers = append(reviewers, r.Name)
}
}
}
}
// 判断mr为零的情况
if mrCount == 0 {
resp = &model.ProjectMrReportResp{}
return
}
if spentTime, err = time.ParseDuration(fmt.Sprintf("%ds", mrTime)); err != nil {
return
}
if avarageTime, err = time.ParseDuration(fmt.Sprintf("%ds", mrTime/mrCount)); err != nil {
return
}
if reviewTotalTime, err = time.ParseDuration(fmt.Sprintf("%ds", reviewTime)); err != nil {
return
}
resp = &model.ProjectMrReportResp{
ChangeAdd: changeAdd,
ChangeDel: changeDel,
MrCount: mrCount,
SpentTime: spentTime.String(),
StateCount: stateCount,
AverageMerge: avarageTime.String(),
Reviewers: reviewers,
Discussion: discussionCount,
Resolve: discussionResolved,
ReviewerOther: reviews,
ReviewChangeAdd: reviewChangeAdd,
ReviewChangeDel: reviewChangeDel,
ReviewTotalTime: reviewTotalTime.String(),
}
return
}
// QueryAllMergeRequestInfo ...
func (s *Service) QueryAllMergeRequestInfo(c context.Context, projID int) (info []*model.MrInfo, err error) {
var (
until = time.Now()
since = until.AddDate(0, -1, 0)
mrs []*gitlab.MergeRequest
resp *gitlab.Response
)
for page := 1; ; page++ {
if mrs, resp, err = s.gitlab.ListProjectMergeRequests(projID, &since, &until, page); err != nil {
return
}
for _, m := range mrs {
mr := &model.MrInfo{ProjectID: projID, MrID: m.IID, Author: m.Author.Name, State: m.State}
if m.State == model.StatusMerged {
spent := m.UpdatedAt.Sub(*m.CreatedAt)
mr.SpentTime = int(spent.Seconds())
}
if mr.ChangeAdd, mr.ChangeDel, err = s.QueryMergeRequestDiff(c, projID, m.IID); err != nil {
return
}
if mr.Reviewers, err = s.QueryMergeRequestReview(c, projID, m.IID); err != nil {
return
}
if mr.TotalDiscussion, mr.SolvedDiscussion, err = s.QueryMergeRequestDiscussion(c, projID, m.IID); err != nil {
return
}
info = append(info, mr)
}
if resp.NextPage == 0 {
break
}
}
return
}
// QueryMergeRequestReview 查询mr reviewer信息
func (s *Service) QueryMergeRequestReview(c context.Context, projectID, mrIID int) (reviewers []*model.MrReviewer, err error) {
var (
notes []*model.StatisticsNotes
emojis []*model.StatisticsMRAwardEmojis
owners []string
r *regexp.Regexp
)
//query note
if notes, err = s.dao.NoteByMRIID(c, projectID, mrIID); err != nil {
return
}
// query emoji
if emojis, err = s.dao.AwardEmojiByMRIID(c, projectID, mrIID); err != nil {
return
}
//评论中解析获取owner
if len(notes) == 0 {
return
}
if r, err = regexp.Compile("OWNER:.*?@(.*)(|)"); err == nil {
matchResult := r.FindStringSubmatch(notes[len(notes)-1].Body)
if len(matchResult) > 0 {
owners = strings.Split(matchResult[1], " 或 @")
}
}
mrCreatedAt := notes[len(notes)-1].CreatedAt
// 从评论和表情中解析 reviewers
for _, note := range notes {
if note.Body == "+1" {
reviewers = append(reviewers, &model.MrReviewer{Name: note.AuthorName, FinishedAt: note.CreatedAt, SpentTime: int(note.CreatedAt.Sub(*mrCreatedAt))})
}
}
for _, emoji := range emojis {
if emoji.Name == "thumbsup" {
reviewers = append(reviewers, &model.MrReviewer{Name: emoji.UserName, FinishedAt: emoji.CreatedAt, SpentTime: int(emoji.CreatedAt.Sub(*mrCreatedAt))})
}
}
// 判断reviewer类型
for _, r := range reviewers {
for _, owner := range owners {
if owner == r.Name {
r.UserType = "owner"
break
}
}
if r.UserType == "" {
r.UserType = "other"
}
}
return
}
// QueryMergeRequestDiscussion 查询获取mr的discussion
func (s *Service) QueryMergeRequestDiscussion(c context.Context, projectID, mrIID int) (total, solved int, err error) {
var discussions []*model.StatisticsDiscussions
if discussions, err = s.dao.DiscussionsByMRIID(c, projectID, mrIID); err != nil {
return
}
for _, d := range discussions {
var notesArray []int
if err = json.Unmarshal([]byte(d.Notes), &notesArray); err != nil {
return
}
for _, n := range notesArray {
var note *model.StatisticsNotes
if note, err = s.dao.NoteByID(c, projectID, mrIID, n); err != nil {
return
}
if note.Resolvable {
total++
if note.Resolved {
solved++
}
}
}
}
return
}
// QueryMergeRequestDiff 查询获取到mr修改文件的总行数, 参数 p project_ID m MR_ID
func (s *Service) QueryMergeRequestDiff(c context.Context, p, m int) (ChangeAdd, ChangeDel int, err error) {
var mr *gitlab.MergeRequest
// 此MR数据量太大曾导致过gitlab服务器崩溃访问会返回502服务器错误因此特殊处理。
if m == _specialMrID {
return 0, 0, nil
}
if mr, _, err = s.gitlab.GetMergeRequestDiff(p, m); err != nil {
return
}
for _, change := range mr.Changes {
rows := strings.Split(change.Diff, "\n")
if len(rows) < 3 {
// 处理diff为空
continue
}
for _, row := range rows[3:] {
if strings.HasPrefix(row, "+") {
ChangeAdd++
} else if strings.HasPrefix(row, "-") {
ChangeDel++
}
}
}
return
}
/*-------------------------------------- sync MR ----------------------------------------*/
// SyncProjectMR ...
func (s *Service) SyncProjectMR(c context.Context, projectID int) (result *model.SyncResult, err error) {
var (
//syncAllTime = conf.Conf.Property.SyncData.SyncAllTime
syncAllTime = false
mrs []*gitlab.MergeRequest
resp *gitlab.Response
since *time.Time
until *time.Time
projectInfo *model.ProjectInfo
)
result = &model.SyncResult{}
if projectInfo, err = s.dao.ProjectInfoByID(projectID); err != nil {
return
}
if !syncAllTime {
since, until = utils.CalSyncTime()
}
log.Info("sync project(%d) MR time since: %v, until: %v", projectID, since, until)
for page := 1; ; page++ {
result.TotalPage++
if mrs, resp, err = s.gitlab.ListProjectMergeRequests(projectID, since, until, page); err != nil {
return
}
for _, mr := range mrs {
if err = s.structureDatabaseMR(c, projectID, projectInfo.Name, mr); err != nil {
log.Error("mr Save Database err: projectID(%d), MRIID(%d)", projectID, mr.IID)
err = nil
errData := &model.FailData{
ChildID: mr.IID,
}
result.FailData = append(result.FailData, errData)
continue
}
result.TotalNum++
}
if resp.NextPage == 0 {
break
}
}
return
}
// structureDatabaseMR ...
func (s *Service) structureDatabaseMR(c context.Context, projectID int, projectName string, mr *gitlab.MergeRequest) (err error) {
var (
milestoneID int
mrLables string
mrChanges string
mrLablesByte []byte
mrChangesByte []byte
)
if mr.Milestone != nil {
milestoneID = mr.Milestone.ID
}
if mrLablesByte, err = json.Marshal(mr.Labels); err != nil {
mrLables = model.JsonMarshalErrorText
} else {
mrLables = string(mrLablesByte)
}
if mrChangesByte, err = json.Marshal(mr.Changes); err != nil {
mrChanges = model.JsonMarshalErrorText
} else {
mrChanges = string(mrChangesByte)
}
mrDB := &model.StatisticsMrs{
MRID: mr.ID,
MRIID: mr.IID,
TargetBranch: mr.TargetBranch,
SourceBranch: mr.SourceBranch,
ProjectID: mr.ProjectID,
ProjectName: projectName,
Title: mr.Title,
State: mr.State,
CreatedAt: mr.CreatedAt,
UpdatedAt: mr.UpdatedAt,
Upvotes: mr.Upvotes,
Downvotes: mr.Downvotes,
AuthorID: mr.Author.ID,
AuthorName: mr.Author.Name,
AssigneeID: mr.Assignee.ID,
AssigneeName: mr.Assignee.Name,
SourceProjectID: mr.SourceProjectID,
TargetProjectID: mr.TargetProjectID,
Labels: mrLables,
Description: mr.Description,
WorkInProgress: mr.WorkInProgress,
MilestoneID: milestoneID,
MergeWhenPipelineSucceeds: mr.MergeWhenPipelineSucceeds,
MergeStatus: mr.MergeStatus,
MergedByID: mr.MergedBy.ID,
MergedByName: mr.MergedBy.Name,
MergedAt: mr.MergedAt,
ClosedByID: mr.ClosedBy.ID,
ClosedAt: mr.ClosedAt,
Subscribed: mr.Subscribed,
SHA: mr.SHA,
MergeCommitSHA: mr.MergeCommitSHA,
UserNotesCount: mr.UserNotesCount,
ChangesCount: mr.ChangesCount,
ShouldRemoveSourceBranch: mr.ShouldRemoveSourceBranch,
ForceRemoveSourceBranch: mr.ForceRemoveSourceBranch,
WebURL: mr.WebURL,
DiscussionLocked: mr.DiscussionLocked,
Changes: mrChanges,
TimeStatsHumanTimeEstimate: mr.TimeStats.HumanTimeEstimate,
TimeStatsHumanTotalTimeSpent: mr.TimeStats.HumanTotalTimeSpent,
TimeStatsTimeEstimate: mr.TimeStats.TimeEstimate,
TimeStatsTotalTimeSpent: mr.TimeStats.TotalTimeSpent,
Squash: mr.Squash,
PipelineID: mr.Pipeline.ID,
}
if len(mrDB.Labels) > model.MessageMaxLen {
mrDB.Labels = mrDB.Labels[0 : model.MessageMaxLen-1]
}
if len(mrDB.Description) > model.MessageMaxLen {
mrDB.Description = mrDB.Description[0 : model.MessageMaxLen-1]
}
return s.SaveDatabaseMR(c, mrDB)
}
// SaveDatabaseMR ...
func (s *Service) SaveDatabaseMR(c context.Context, mrDB *model.StatisticsMrs) (err error) {
var total int
if total, err = s.dao.HasMR(c, mrDB.ProjectID, mrDB.MRIID); err != nil {
log.Error("SaveDatabaseMR HasMR(%+v)", err)
return
}
// found only one, so update
if total == 1 {
return s.dao.UpdateMR(c, mrDB.ProjectID, mrDB.MRIID, mrDB)
} else if total > 1 {
// found repeated row, this situation will not exist under normal
log.Warn("SaveDatabaseMR mr has more rows(%d)", total)
return
}
// insert row now
return s.dao.CreateMR(c, mrDB)
}
/*-------------------------------------- agg MR ----------------------------------------*/
// AggregateProjectMR ...
func (s *Service) AggregateProjectMR(c context.Context, projectID int) (err error) {
var (
//syncAllTime = conf.Conf.Property.SyncData.SyncAllTime
syncAllTime = false
mrs []*model.StatisticsMrs
projectInfo *model.ProjectInfo
since *time.Time
until *time.Time
)
if projectInfo, err = s.dao.ProjectInfoByID(projectID); err != nil {
return
}
if !syncAllTime {
since, until = utils.CalSyncTime()
}
if mrs, err = s.dao.MRByProjectID(c, projectID, since, until); err != nil {
return
}
for _, mr := range mrs {
if err = s.MRAddedInfoDB(c, projectID, mr); err != nil {
log.Error("MRAddedInfoDB Save Database err: projectID(%d), MRIID(%d)", projectID, mr.MRIID)
err = nil
}
if err = s.MRReviewerDB(c, projectID, mr.MRIID, projectInfo.Name, mr); err != nil {
log.Error("MRReviewerDB Save Database err: projectID(%d), MRIID(%d)", projectID, mr.MRIID)
err = nil
}
}
return
}
// MRAddedInfoDB ...
func (s *Service) MRAddedInfoDB(c context.Context, projectID int, mr *model.StatisticsMrs) (err error) {
if mr.ChangeAdd, mr.ChangeDel, err = s.QueryMergeRequestDiff(c, projectID, mr.MRIID); err != nil {
return
}
if mr.TotalDiscussion, mr.SolvedDiscussion, err = s.QueryMergeRequestDiscussion(c, projectID, mr.MRIID); err != nil {
return
}
return s.dao.UpdateMR(c, projectID, mr.MRIID, mr)
}
// MRReviewerDB 查询mr reviewer信息
func (s *Service) MRReviewerDB(c context.Context, projectID, mrIID int, projectName string, mr *model.StatisticsMrs) (err error) {
var (
notes []*model.StatisticsNotes
emojis []*model.StatisticsMRAwardEmojis
owners []string
r *regexp.Regexp
reviewers []*model.AggregateMrReviewer
)
//query note
if notes, err = s.dao.NoteByMRIID(c, projectID, mrIID); err != nil {
return
}
// query emoji
if emojis, err = s.dao.AwardEmojiByMRIID(c, projectID, mrIID); err != nil {
return
}
//评论中解析获取owner
if len(notes) == 0 {
return
}
if r, err = regexp.Compile("OWNER:.*?@(.*)(|)"); err == nil {
matchResult := r.FindStringSubmatch(notes[len(notes)-1].Body)
if len(matchResult) > 0 {
owners = strings.Split(matchResult[1], " 或 @")
}
}
mrCreatedAt := notes[len(notes)-1].CreatedAt
// 从评论和表情中解析 reviewers
for _, note := range notes {
if note.Body == "+1" {
reviewer := &model.AggregateMrReviewer{
ReviewerID: note.AuthorID,
ReviewerName: note.AuthorName,
ReviewType: "note",
ReviewID: note.NoteID,
ReviewCommand: "+1",
CreatedAt: note.CreatedAt,
ApproveTime: int(note.CreatedAt.Sub(*mrCreatedAt).Seconds()),
MergeTime: int(mr.UpdatedAt.Sub(*note.CreatedAt).Seconds()),
}
reviewers = append(reviewers, reviewer)
}
}
for _, emoji := range emojis {
if emoji.Name == "thumbsup" {
reviewer := &model.AggregateMrReviewer{
ReviewerID: emoji.UserID,
ReviewerName: emoji.UserName,
ReviewType: "emoji",
ReviewID: emoji.AwardEmojiID,
ReviewCommand: "thumbsup",
CreatedAt: emoji.CreatedAt,
ApproveTime: int(emoji.CreatedAt.Sub(*mrCreatedAt).Seconds()),
MergeTime: int(mr.UpdatedAt.Sub(*emoji.CreatedAt).Seconds()),
}
reviewers = append(reviewers, reviewer)
}
}
// 判断reviewer类型
for _, r := range reviewers {
r.ProjectID = projectID
r.ProjectName = projectName
r.MrIID = mrIID
r.Title = mr.Title
r.WebUrl = mr.WebURL
r.AuthorName = mr.AuthorName
if utils.InSlice(r.ReviewerName, owners) {
r.UserType = "owner"
} else {
r.UserType = "other"
}
if err = s.SaveDatabaseAggMR(c, r); err != nil {
log.Error("mrReviewer 存数据库报错(%+V)", err)
continue
}
}
return
}
// SaveDatabaseAggMR ...
func (s *Service) SaveDatabaseAggMR(c context.Context, mrDB *model.AggregateMrReviewer) (err error) {
var total int
if total, err = s.dao.HasAggregateReviewer(c, mrDB.ProjectID, mrDB.MrIID, mrDB.ReviewerID, mrDB.ReviewID); err != nil {
log.Error("SaveDatabaseAggMR HasAggMR(%+v)", err)
return
}
// found only one, so update
if total == 1 {
return s.dao.UpdateAggregateReviewer(c, mrDB.ProjectID, mrDB.MrIID, mrDB.ReviewerID, mrDB.ReviewID, mrDB)
} else if total > 1 {
// found repeated row, this situation will not exist under normal
log.Warn("SaveDatabaseAggMR aggMR has more rows(%d)", total)
return
}
// insert row now
return s.dao.CreateAggregateReviewer(c, mrDB)
}

View File

@@ -0,0 +1,838 @@
package service
import (
"context"
"fmt"
"strings"
"time"
"go-common/app/admin/ep/saga/conf"
"go-common/app/admin/ep/saga/model"
"go-common/app/admin/ep/saga/service/utils"
"go-common/app/admin/ep/saga/service/wechat"
"go-common/library/cache/memcache"
"go-common/library/log"
"github.com/xanzy/go-gitlab"
)
const (
_gitHTTP = "http://git.bilibili.co/"
_gitSSH = "git@git-test.bilibili.co:"
_gitSSHTail = ".git"
_manualJob = "manual"
_androidScheduleJob = "daily branch check"
_iosScheduleJob = "daily:on-schedule"
)
// QueryTeamPipeline query pipeline info according to team.
func (s *Service) QueryTeamPipeline(c context.Context, req *model.TeamDataRequest) (resp *model.PipelineDataResp, err error) {
var (
projectInfo []*model.ProjectInfo
reqProject = &model.ProjectInfoRequest{}
data []*model.PipelineDataTime
queryDes string
total int
succNum int
key string
keyNotExist bool
)
if len(req.Department) <= 0 && len(req.Business) <= 0 {
log.Warn("query department and business are empty!")
return
}
//get pipeline info from mc
key = "saga_admin_" + req.Department + "_" + req.Business + "_" + model.KeyTypeConst[3]
if resp, err = s.dao.GetPipeline(c, key); err != nil {
if err == memcache.ErrNotFound {
keyNotExist = true
} else {
return
}
} else {
return
}
log.Info("sync team pipeline start => type= %d, Department= %s, Business= %s", req.QueryType, req.Department, req.Business)
//query team projects
reqProject.Department = req.Department
reqProject.Business = req.Business
if _, projectInfo, err = s.dao.QueryProjectInfo(false, reqProject); err != nil {
return
}
if len(projectInfo) <= 0 {
log.Warn("Found no project!")
return
}
if data, total, succNum, err = s.QueryTeamPipelineByTime(projectInfo, model.LastWeekPerDay); err != nil {
return
}
successScale := succNum * 100 / total
queryDes = req.Department + " " + req.Business + " " + "pipeline上一周每天数量"
resp = &model.PipelineDataResp{
Department: req.Department,
Business: req.Business,
QueryDes: queryDes,
Total: total,
SuccessNum: succNum,
SuccessScale: successScale,
Data: data,
}
//set pipeline info to mc
if keyNotExist {
if err = s.dao.SetPipeline(c, key, resp); err != nil {
return
}
}
log.Info("sync team pipeline end")
return
}
// QueryTeamPipelineByTime ...
func (s *Service) QueryTeamPipelineByTime(projectInfo []*model.ProjectInfo, queryType int) (resp []*model.PipelineDataTime, allNum, succNum int, err error) {
var (
layout = "2006-01-02"
since time.Time
until time.Time
total int
success int
count int
)
if queryType == model.LastWeekPerDay {
count = model.DayNumPerWeek
} else {
log.Warn("Query Type is not in range!")
return
}
year, month, day := time.Now().Date()
weekDay := (int)(time.Now().Weekday())
today := time.Date(year, month, day, 0, 0, 0, 0, time.Local)
for i := 0; i < count; i++ {
since = today.AddDate(0, 0, -weekDay-i)
until = today.AddDate(0, 0, -weekDay-i+1)
totalAll := 0
successAll := 0
//log.Info("== start query from: %v, to: %v", since, until)
for _, project := range projectInfo {
if total, success, err = s.QueryProjectPipeline(project.ProjectID, "success", since, until); err != nil {
return
}
totalAll = totalAll + total
successAll = successAll + success
}
perData := &model.PipelineDataTime{
TotalItem: totalAll,
SuccessItem: successAll,
StartTime: since.Format(layout),
EndTime: until.Format(layout),
}
resp = append(resp, perData)
allNum = allNum + totalAll
succNum = succNum + successAll
}
return
}
// QueryProjectPipeline query pipeline info according to project id.
func (s *Service) QueryProjectPipeline(projectID int, state string, since, until time.Time) (totalNum, stateNum int, err error) {
var (
pipelineList gitlab.PipelineList
pipeline *gitlab.Pipeline
resp *gitlab.Response
startQuery bool
)
if _, resp, err = s.gitlab.ListProjectPipelines(1, projectID, ""); err != nil {
return
}
page := 1
for page <= resp.TotalPages {
if !startQuery {
if pipelineList, _, err = s.gitlab.ListProjectPipelines(page, projectID, ""); err != nil {
return
}
if page == 1 && len(pipelineList) <= 0 {
return
}
if pipeline, _, err = s.gitlab.GetPipeline(projectID, pipelineList[0].ID); err != nil {
return
}
if pipeline.CreatedAt.After(until) {
page++
continue
} else {
startQuery = true
page--
continue
}
}
if pipelineList, _, err = s.gitlab.ListProjectPipelines(page, projectID, ""); err != nil {
return
}
for _, v := range pipelineList {
if pipeline, _, err = s.gitlab.GetPipeline(projectID, v.ID); err != nil {
return
}
createTime := pipeline.CreatedAt
//year, month, day := createTime.Date()
//log.Info("index: %d createTime: %d, month: %d, day: %d", k, year, month, day)
if createTime.After(since) && createTime.Before(until) {
totalNum = totalNum + 1
if pipeline.Status == state {
stateNum = stateNum + 1
}
}
if createTime.Before(since) {
return
}
}
page++
}
return
}
// QueryProjectPipelineNew ...
func (s *Service) QueryProjectPipelineNew(c context.Context, req *model.PipelineDataReq) (resp *model.PipelineDataAvgResp, err error) {
var (
data []*model.PipelineDataAvg
queryDes string
total int
totalStatus int
avgDurationTime float64
avgPendingTime float64
avgRunningTime float64
)
log.Info("QuerySingleProjectData Type: %d", req.Type)
switch req.Type {
case model.LastYearPerMonth:
queryDes = model.LastYearPerMonthNote
case model.LastMonthPerDay:
queryDes = model.LastMonthPerDayNote
case model.LastYearPerDay:
queryDes = model.LastYearPerDayNote
default:
log.Warn("QueryProjectCommit Type is not in range")
return
}
queryDes = req.ProjectName + " pipeline " + req.State + " " + queryDes
if data, total, totalStatus, avgDurationTime, avgPendingTime, avgRunningTime, err = s.QueryProjectByTimeNew(c, req, req.Type); err != nil {
return
}
resp = &model.PipelineDataAvgResp{
ProjectName: req.ProjectName,
QueryDes: queryDes,
Status: req.State,
Total: total,
TotalStatus: totalStatus,
AvgDurationTime: avgDurationTime,
AvgPendingTime: avgPendingTime,
AvgRunningTime: avgRunningTime,
Data: data,
}
return
}
// QueryProjectByTimeNew ...
func (s *Service) QueryProjectByTimeNew(c context.Context, req *model.PipelineDataReq, queryType int) (resp []*model.PipelineDataAvg, allNum, allStatusNum int, avgDurationTime, avgPendingTime, avgRunningTime float64, err error) {
var (
layout = "2006-01-02"
since time.Time
until time.Time
count int
pendingTimeListAll []float64
runningTimeListAll []float64
durationTimeListAll []float64
pipelineTime *model.PipelineTime
perData *model.PipelineDataAvg
avgTotalTime float64
totalNum int
statusNum int
)
year, month, day := time.Now().Date()
thisMonth := time.Date(year, month, day, 0, 0, 0, 0, time.Local)
if queryType == model.LastYearPerMonth {
count = model.MonthNumPerYear
} else if queryType == model.LastMonthPerDay {
//_, _, count = thisMonth.AddDate(0, 0, -1).Date()
count = model.DayNumPerMonth
} else if queryType == model.LastYearPerDay {
count = model.DayNumPerYear
}
for i := count; i >= 1; i-- {
if queryType == model.LastYearPerMonth {
since = thisMonth.AddDate(0, -i, 0)
until = thisMonth.AddDate(0, -i+1, 0)
} else if queryType == model.LastMonthPerDay {
since = thisMonth.AddDate(0, 0, -i)
until = thisMonth.AddDate(0, 0, -i+1)
} else if queryType == model.LastYearPerDay {
since = thisMonth.AddDate(0, 0, -i)
until = thisMonth.AddDate(0, 0, -i+1)
}
/*if totalNum, statusNum, pipelineTime, err = s.QueryProjectPipelines(c, req, since, until); err != nil {
log.Error("QueryProjectPipelines err%+v", err)
return
}*/
if totalNum, statusNum, pipelineTime, err = s.QueryPipelinesFromDB(c, req, since, until); err != nil {
log.Error("QueryPipelinesFromDB err%+v", err)
return
}
avgTotalTime = utils.CalAverageTime(req.StatisticsType, pipelineTime.DurationList)
avgPendingTime = utils.CalAverageTime(req.StatisticsType, pipelineTime.PendingList)
avgRunningTime = utils.CalAverageTime(req.StatisticsType, pipelineTime.RunningList)
perData = &model.PipelineDataAvg{
TotalItem: totalNum,
TotalStatusItem: statusNum,
AvgDurationTime: avgTotalTime,
AvgPendingTime: avgPendingTime,
AvgRunningTime: avgRunningTime,
MaxDurationTime: pipelineTime.DurationMax,
MinDurationTime: pipelineTime.DurationMin,
MaxPendingTime: pipelineTime.PendingMax,
MinPendingTime: pipelineTime.PendingMin,
MaxRunningTime: pipelineTime.RunningMax,
MinRunningTime: pipelineTime.RunningMin,
StartTime: since.Format(layout),
EndTime: until.Format(layout),
}
resp = append(resp, perData)
allNum = allNum + totalNum
allStatusNum = allStatusNum + statusNum
pendingTimeListAll = utils.CombineSlice(pendingTimeListAll, pipelineTime.PendingList)
runningTimeListAll = utils.CombineSlice(runningTimeListAll, pipelineTime.RunningList)
durationTimeListAll = utils.CombineSlice(durationTimeListAll, pipelineTime.DurationList)
}
avgDurationTime = utils.CalAverageTime(req.StatisticsType, durationTimeListAll)
avgPendingTime = utils.CalAverageTime(req.StatisticsType, pendingTimeListAll)
avgRunningTime = utils.CalAverageTime(req.StatisticsType, runningTimeListAll)
log.Info("avgDurationTime: %v, avgPendingTime: %v, avgRunningTime: %v", avgDurationTime, avgPendingTime, avgRunningTime)
return
}
// QueryProjectPipelines ...
func (s *Service) QueryProjectPipelines(c context.Context, req *model.PipelineDataReq, since, until time.Time) (totalNum, statusNum int, pipelineTime *model.PipelineTime, err error) {
var (
pipelineList gitlab.PipelineList
pipeline *gitlab.Pipeline
resp *gitlab.Response
startQuery bool
meetTime bool
projectID = req.ProjectID
pendingTime float64
runningTime float64
totalTime float64
)
pipelineTime = &model.PipelineTime{}
opt := &gitlab.ListProjectPipelinesOptions{}
if _, resp, err = s.gitlab.ListProjectPipelines(1, projectID, ""); err != nil {
log.Error("ListProjectPipelines err: %+v", err)
return
}
page := 1
for page <= resp.TotalPages {
opt.ListOptions.Page = page
if !startQuery && (!since.IsZero() || !until.IsZero()) {
if pipelineList, _, err = s.gitlab.ListProjectPipelines(page, projectID, ""); err != nil {
log.Error("ListProjectPipelines err: %+v", err)
return
}
if page == 1 && len(pipelineList) <= 0 {
return
}
if pipeline, _, err = s.gitlab.GetPipeline(projectID, pipelineList[0].ID); err != nil {
return
}
if pipeline.CreatedAt.After(until) {
page++
continue
} else {
startQuery = true
page--
continue
}
}
// start query
if pipelineList, _, err = s.gitlab.ListProjectPipelines(page, projectID, ""); err != nil {
return
}
meetTime = true
for _, v := range pipelineList {
if pipeline, _, err = s.gitlab.GetPipeline(projectID, v.ID); err != nil {
return
}
createTime := pipeline.CreatedAt
if !since.IsZero() || !until.IsZero() {
meetTime = createTime.After(since) && createTime.Before(until)
}
//the pipeline is we need
if meetTime {
totalNum = totalNum + 1
if req.Branch != "" && req.Branch != pipeline.Ref {
continue
} else if req.User != "" && req.User != pipeline.User.Name {
continue
} else if req.State != "" && req.State != pipeline.Status {
continue
}
statusNum = statusNum + 1
if pipeline.Status != "cancel" {
if pipeline.StartedAt == nil {
pendingTime = 0
runningTime = pipeline.FinishedAt.Sub(*pipeline.CreatedAt).Seconds()
} else {
pendingTime = pipeline.StartedAt.Sub(*pipeline.CreatedAt).Seconds()
runningTime = pipeline.FinishedAt.Sub(*pipeline.StartedAt).Seconds()
}
totalTime = pipeline.FinishedAt.Sub(*pipeline.CreatedAt).Seconds()
pipelineTime.PendingMax, pipelineTime.PendingMin = utils.CalSizeTime(pendingTime, pipelineTime.PendingMax, pipelineTime.PendingMin)
pipelineTime.RunningMax, pipelineTime.RunningMin = utils.CalSizeTime(runningTime, pipelineTime.RunningMax, pipelineTime.RunningMin)
pipelineTime.DurationMax, pipelineTime.DurationMin = utils.CalSizeTime(totalTime, pipelineTime.DurationMax, pipelineTime.DurationMin)
pipelineTime.PendingList = append(pipelineTime.PendingList, pendingTime)
pipelineTime.RunningList = append(pipelineTime.RunningList, runningTime)
pipelineTime.DurationList = append(pipelineTime.DurationList, totalTime)
}
}
// time is over, so return
if (!since.IsZero() || !until.IsZero()) && createTime.Before(since) {
return
}
}
page++
}
return
}
// QueryPipelinesFromDB ...
func (s *Service) QueryPipelinesFromDB(c context.Context, req *model.PipelineDataReq, since, until time.Time) (totalNum, statusNum int, pipelineTime *model.PipelineTime, err error) {
var (
fmtLayout = `%d-%d-%d 00:00:00`
pipelines []*model.StatisticsPipeline
projectID = req.ProjectID
pendingTime float64
runningTime float64
totalTime float64
)
pipelineTime = &model.PipelineTime{}
sinceStr := fmt.Sprintf(fmtLayout, since.Year(), since.Month(), since.Day())
untilStr := fmt.Sprintf(fmtLayout, until.Year(), until.Month(), until.Day())
if totalNum, statusNum, pipelines, err = s.dao.QueryPipelinesByTime(projectID, req, sinceStr, untilStr); err != nil {
return
}
for _, pipeline := range pipelines {
if pipeline.Status == model.StatusCancel {
continue
}
if pipeline.FinishedAt == nil {
continue
}
if pipeline.StartedAt == nil {
pendingTime = 0
runningTime = pipeline.FinishedAt.Sub(*pipeline.CreatedAt).Seconds()
} else {
pendingTime = pipeline.StartedAt.Sub(*pipeline.CreatedAt).Seconds()
runningTime = pipeline.FinishedAt.Sub(*pipeline.StartedAt).Seconds()
}
totalTime = pipeline.FinishedAt.Sub(*pipeline.CreatedAt).Seconds()
pipelineTime.PendingMax, pipelineTime.PendingMin = utils.CalSizeTime(pendingTime, pipelineTime.PendingMax, pipelineTime.PendingMin)
pipelineTime.RunningMax, pipelineTime.RunningMin = utils.CalSizeTime(runningTime, pipelineTime.RunningMax, pipelineTime.RunningMin)
pipelineTime.DurationMax, pipelineTime.DurationMin = utils.CalSizeTime(totalTime, pipelineTime.DurationMax, pipelineTime.DurationMin)
pipelineTime.PendingList = append(pipelineTime.PendingList, pendingTime)
pipelineTime.RunningList = append(pipelineTime.RunningList, runningTime)
pipelineTime.DurationList = append(pipelineTime.DurationList, totalTime)
}
return
}
//alertProjectPipelineProc cron func
func (s *Service) alertProjectPipelineProc() {
for _, alert := range conf.Conf.Property.Git.AlertPipeline {
projectId := alert.ProjectID
runningTimeout := alert.RunningTimeout
runningRate := alert.RunningRate
runningThreshold := alert.RunningThreshold
pendingTimeout := alert.PendingTimeout
pendingThreshold := alert.PendingThreshold
go func() {
var err error
if err = s.PipelineAlert(context.TODO(), projectId, runningTimeout, runningThreshold, runningRate, gitlab.Running); err != nil {
log.Error("PipelineAlert Running (%+v)", err)
}
if err = s.PipelineAlert(context.TODO(), projectId, pendingTimeout, pendingThreshold, 0, gitlab.Pending); err != nil {
log.Error("PipelineAlert Pending (%+v)", err)
}
}()
}
}
//PipelineAlert ...
func (s *Service) PipelineAlert(c context.Context, projectID, timeout, threshold, rate int, status gitlab.BuildStateValue) (err error) {
var (
layout = "2006-01-02 15:04:05"
pipeline *gitlab.Pipeline
timeoutNum int
message string
pipelineurl string
durationTime float64
pipelineSUM int
timeoutPipeline string
pipelineList gitlab.PipelineList
resp *gitlab.Response
projectInfo *model.ProjectInfo
userlist = conf.Conf.Property.Git.UserList
w = wechat.New(s.dao)
sendMessage = false
)
if projectInfo, err = s.dao.ProjectInfoByID(projectID); err != nil {
return
}
repo := projectInfo.Repo
if len(repo) > len(_gitSSH) {
repo = repo[len(_gitSSH) : len(repo)-len(_gitSSHTail)]
repo = _gitHTTP + repo
}
timeNow := time.Now().Format(layout)
message = fmt.Sprintf("[SAGA]Pipeline 告警 %v\n项目%s\n", timeNow, repo)
for page := 1; ; page++ {
if pipelineList, resp, err = s.git.ListProjectPipelines(page, projectID, status); err != nil {
return
}
for _, item := range pipelineList {
pipelineSUM += 1
if pipeline, _, err = s.git.GetPipeline(projectID, item.ID); err != nil {
return
}
if status == gitlab.Pending {
durationTime = pipeline.UpdatedAt.Sub(*pipeline.CreatedAt).Minutes()
} else if status == gitlab.Running {
//此处时间计算换成job
if durationTime, err = s.PipelineRunningTime(projectID, item.ID); err != nil {
return
}
}
if int(durationTime) >= timeout {
timeoutNum += 1
pipelineurl = fmt.Sprintf("%d. %s/pipelines/%d (%vmin)\n", timeoutNum, repo, pipeline.ID, int(durationTime))
timeoutPipeline += pipelineurl
}
}
if resp.NextPage == 0 {
break
}
}
if timeoutPipeline != "" {
message += fmt.Sprintf(`列表(url|%s时间):%s%s`, status, "\n", timeoutPipeline)
}
if pipelineSUM > 0 {
message += fmt.Sprintf(`状态:%s 总数为%d个`, status, pipelineSUM)
}
if status == gitlab.Pending {
var alertMessage string
message += fmt.Sprintf(`%s告警`, "\n")
if pipelineSUM >= threshold {
alertMessage = fmt.Sprintf(`[ 数量(%d>=警戒值(%d ]`, pipelineSUM, threshold)
sendMessage = true
}
message += alertMessage
if timeoutNum >= 1 {
if alertMessage != "" {
message = message[:strings.LastIndex(message, " ]")] + fmt.Sprintf(`%s时间>=警戒值(%d ]`, status, timeout)
} else {
message += fmt.Sprintf(`[ %s时间>=警戒值(%d ]`, status, timeout)
}
sendMessage = true
}
}
if status == gitlab.Running && timeoutNum >= threshold {
sendMessage = true
message += fmt.Sprintf(`,时间>%dmin为%d个%s告警[ 数量(%d>=警戒值(%d) ]`, timeout, timeoutNum, "\n", timeoutNum, threshold)
if timeoutNum*100/pipelineSUM >= rate {
message = message[:strings.LastIndex(message, " ]")] + fmt.Sprintf(`,比例(%v%s>=警戒值%d%s ]`, timeoutNum*100/pipelineSUM, "%", rate, "%")
}
}
if sendMessage {
return w.PushMsg(c, userlist, message)
}
return
}
//PipelineRunningTime ...
func (s *Service) PipelineRunningTime(projectID, pipelineID int) (durationTime float64, err error) {
var jobList []*gitlab.Job
if jobList, _, err = s.git.ListPipelineJobs(nil, projectID, pipelineID); err != nil {
return
}
for _, job := range jobList {
if job.Status != _manualJob && job.Name != _androidScheduleJob && job.Name != _iosScheduleJob {
if job.FinishedAt != nil && job.StartedAt != nil {
durationTime += job.FinishedAt.Sub(*job.StartedAt).Minutes()
} else if job.StartedAt != nil {
durationTime += time.Since(*job.StartedAt).Minutes()
}
}
}
return
}
/*-------------------------------------- sync pipeline ----------------------------------------*/
// SyncProjectPipelines ...
func (s *Service) SyncProjectPipelines(projectID int) (result *model.SyncResult, err error) {
var (
//syncAllTime = conf.Conf.Property.SyncData.SyncAllTime
syncAllTime = false
since *time.Time
until *time.Time
projectInfo *model.ProjectInfo
)
if projectInfo, err = s.dao.ProjectInfoByID(projectID); err != nil {
return
}
if !syncAllTime {
since, until = utils.CalSyncTime()
log.Info("sync project id(%d), name(%s) pipeline, time since: %v, until: %v", projectID, projectInfo.Name, since, until)
if result, err = s.SyncProjectPipelinesByTime(projectID, projectInfo.Name, *since, *until); err != nil {
return
}
} else {
log.Info("sync project id(%d), name(%s) pipeline", projectID, projectInfo.Name)
if result, err = s.SyncProjectAllPipelines(projectID, projectInfo.Name); err != nil {
return
}
}
return
}
// SyncProjectPipelinesByTime ...
func (s *Service) SyncProjectPipelinesByTime(projectID int, projectName string, since, until time.Time) (result *model.SyncResult, err error) {
var (
pipelines gitlab.PipelineList
pipeline *gitlab.Pipeline
resp *gitlab.Response
startQuery bool
)
result = &model.SyncResult{}
if _, resp, err = s.gitlab.ListProjectPipelines(1, projectID, ""); err != nil {
return
}
page := 1
for page <= resp.TotalPages {
result.TotalPage++
if !startQuery {
if pipelines, resp, err = s.gitlab.ListProjectPipelines(page, projectID, ""); err != nil {
return
}
if page == 1 && len(pipelines) <= 0 {
return
}
if pipeline, _, err = s.gitlab.GetPipeline(projectID, pipelines[0].ID); err != nil {
return
}
if pipeline.CreatedAt.After(until) {
page++
continue
} else {
startQuery = true
page--
continue
}
}
// start query
if pipelines, _, err = s.gitlab.ListProjectPipelines(page, projectID, ""); err != nil {
return
}
for _, v := range pipelines {
if pipeline, _, err = s.gitlab.GetPipeline(projectID, v.ID); err != nil {
return
}
createTime := pipeline.CreatedAt
if createTime.After(since) && createTime.Before(until) {
if err = s.structureDBPipeline(projectID, projectName, pipeline); err != nil {
log.Error("pipeline Save Database err: projectID(%d), PipelineID(%d)", projectID, pipeline.ID)
err = nil
errData := &model.FailData{
ChildID: pipeline.ID,
}
result.FailData = append(result.FailData, errData)
continue
}
result.TotalNum++
}
if createTime.Before(since) {
return
}
}
page++
}
return
}
// SyncProjectAllPipelines ...
func (s *Service) SyncProjectAllPipelines(projectID int, projectName string) (result *model.SyncResult, err error) {
var (
pipelines gitlab.PipelineList
pipeline *gitlab.Pipeline
resp *gitlab.Response
)
result = &model.SyncResult{}
for page := 1; ; page++ {
result.TotalPage++
if pipelines, resp, err = s.gitlab.ListProjectPipelines(page, projectID, ""); err != nil {
return
}
for _, v := range pipelines {
if pipeline, _, err = s.gitlab.GetPipeline(projectID, v.ID); err != nil {
return
}
if err = s.structureDBPipeline(projectID, projectName, pipeline); err != nil {
log.Error("pipeline Save Database err: projectID(%d), PipelineID(%d)", projectID, pipeline.ID)
err = nil
errData := &model.FailData{
ChildID: pipeline.ID,
}
result.FailData = append(result.FailData, errData)
continue
}
result.TotalNum++
}
if resp.NextPage == 0 {
break
}
}
return
}
// structureDBPipeline ...
func (s *Service) structureDBPipeline(projectID int, projectName string, pipeline *gitlab.Pipeline) (err error) {
statisticPipeline := &model.StatisticsPipeline{
PipelineID: pipeline.ID,
ProjectID: projectID,
ProjectName: projectName,
Status: pipeline.Status,
Ref: pipeline.Ref,
Tag: pipeline.Tag,
User: pipeline.User.Name,
CreatedAt: pipeline.CreatedAt,
UpdatedAt: pipeline.UpdatedAt,
StartedAt: pipeline.StartedAt,
FinishedAt: pipeline.FinishedAt,
CommittedAt: pipeline.CommittedAt,
Coverage: pipeline.Coverage,
Duration: pipeline.Duration,
DurationTime: 0,
}
return s.SaveDatabasePipeline(statisticPipeline)
}
// SaveDatabasePipeline ...
func (s *Service) SaveDatabasePipeline(pipelineDB *model.StatisticsPipeline) (err error) {
var total int
if total, err = s.dao.HasPipeline(pipelineDB.ProjectID, pipelineDB.PipelineID); err != nil {
log.Error("SaveDatabasePipeline HasPipeline(%+v)", err)
return
}
// found only one, so update
if total == 1 {
if err = s.dao.UpdatePipeline(pipelineDB.ProjectID, pipelineDB.PipelineID, pipelineDB); err != nil {
log.Error("SaveDatabasePipeline UpdatePipeline err(%+v)", err)
return
}
return
} else if total > 1 {
// found repeated row, this situation will not exist under normal
log.Warn("SaveDatabasePipeline pipeline has more rows(%d)", total)
return
}
// insert row now
if err = s.dao.CreatePipeline(pipelineDB); err != nil {
log.Error("SaveDatabasePipeline CreatePipeline err(%+v)", err)
return
}
return
}

View File

@@ -0,0 +1,43 @@
package service
import (
"context"
"testing"
"go-common/app/admin/ep/saga/model"
"github.com/smartystreets/goconvey/convey"
)
func TestService_QueryProjectRunners(t *testing.T) {
convey.Convey("get saga-admin runners", t, func() {
var (
ctx = context.Background()
req = &model.ProjectDataReq{ProjectID: 682}
)
resp, err := srv.QueryProjectRunners(ctx, req)
convey.So(err, convey.ShouldBeNil)
convey.So(len(resp), convey.ShouldBeGreaterThan, 1)
})
convey.Convey("get saga-admin runners", t, func() {
var (
ctx = context.Background()
req = &model.ProjectDataReq{ProjectID: 4928}
)
resp, err := srv.QueryProjectRunners(ctx, req)
convey.So(err, convey.ShouldBeNil)
convey.So(len(resp), convey.ShouldBeGreaterThan, 1)
})
convey.Convey("get saga-admin runners", t, func() {
var (
ctx = context.Background()
req = &model.ProjectDataReq{ProjectID: 5822}
)
resp, err := srv.QueryProjectRunners(ctx, req)
convey.So(err, convey.ShouldBeNil)
convey.So(len(resp), convey.ShouldBeGreaterThan, 1)
})
}

View File

@@ -0,0 +1,159 @@
package service
import (
"context"
"go-common/app/admin/ep/saga/conf"
"go-common/app/admin/ep/saga/model"
)
// FavoriteProjects list user's favorite projects
func (s *Service) FavoriteProjects(c context.Context, req *model.Pagination, userName string) (resp *model.FavoriteProjectsResp, err error) {
var (
favorite *model.ProjectFavorite
favorites []*model.ProjectFavorite
projectInfo *model.ProjectInfo
)
resp = &model.FavoriteProjectsResp{}
if favorites, err = s.dao.FavoriteProjects(userName); err != nil {
return
}
for _, favorite = range favorites {
if projectInfo, err = s.dao.ProjectInfoByID(favorite.ProjID); err != nil {
return
}
myProject := &model.MyProjectInfo{
ProjectInfo: projectInfo,
}
myProject.Star = true
resp.Projects = append(resp.Projects, myProject)
}
resp.PageSize = req.PageSize
resp.PageNum = req.PageNum
resp.Total = len(favorites)
return
}
// EditFavorite edit user's favorites, star/unstar
func (s *Service) EditFavorite(c context.Context, req *model.EditFavoriteReq, userName string) (resp *model.EmptyResp, err error) {
var (
projID = req.ProjID
favorites []*model.ProjectFavorite
exist bool
)
resp = &model.EmptyResp{}
if favorites, err = s.dao.FavoriteProjects(userName); err != nil {
return
}
if req.Star {
if !inFavorites(favorites, projID) {
if exist, err = s.dao.ProjectExist(projID); err != nil {
return
}
if exist {
if err = s.dao.AddFavorite(userName, projID); err != nil {
return
}
}
}
} else {
if inFavorites(favorites, projID) {
if err = s.dao.DelFavorite(userName, projID); err != nil {
return
}
}
}
return
}
// inFavorites ...
func inFavorites(favorites []*model.ProjectFavorite, projID int) bool {
var (
f *model.ProjectFavorite
)
for _, f = range favorites {
if projID == f.ProjID {
return true
}
}
return false
}
// QueryCommonProjects ...
func (s *Service) QueryCommonProjects(c context.Context) (result []string, err error) {
var (
projectInfo *model.ProjectInfo
)
ids := conf.Conf.Property.DefaultProject.ProjectIDs
for _, id := range ids {
if projectInfo, err = s.dao.ProjectInfoByID(id); err != nil {
return
}
result = append(result, projectInfo.Name)
}
return
}
// QueryProjectInfo query project info.
func (s *Service) QueryProjectInfo(c context.Context, req *model.ProjectInfoRequest) (resp *model.ProjectInfoResp, err error) {
var (
projectInfo []*model.ProjectInfo
project *model.ProjectInfo
total int
saga int
runner int
sagaScale int
runnerScale int
mproject *model.MyProjectInfo
favorites []*model.ProjectFavorite
projectInfoResp []*model.MyProjectInfo
)
userName := req.Username
if total, projectInfo, err = s.dao.QueryProjectInfo(true, req); err != nil {
return
}
if favorites, err = s.dao.FavoriteProjects(userName); err != nil {
return
}
for _, project = range projectInfo {
mproject = &model.MyProjectInfo{
ProjectInfo: project,
}
if !inFavorites(favorites, project.ProjectID) {
mproject.Star = false
} else {
mproject.Star = true
}
projectInfoResp = append(projectInfoResp, mproject)
}
if saga, err = s.dao.QueryConfigInfo(req.Name, req.Department, req.Business, "saga"); err != nil {
return
}
if runner, err = s.dao.QueryConfigInfo(req.Name, req.Department, req.Business, "runner"); err != nil {
return
}
if total != 0 {
sagaScale = saga * 100 / total
runnerScale = runner * 100 / total
}
resp = &model.ProjectInfoResp{
PageNum: req.PageNum,
PageSize: req.PageSize,
Total: total,
Saga: saga,
Runner: runner,
SagaScale: sagaScale,
RunnerScale: runnerScale,
ProjectInfo: projectInfoResp,
}
return
}

View File

@@ -0,0 +1,112 @@
package service
import (
"context"
"go-common/app/admin/ep/saga/model"
"go-common/library/log"
"github.com/xanzy/go-gitlab"
)
//QueryProjectRunners query project runners info according to project id
func (s *Service) QueryProjectRunners(c context.Context, req *model.ProjectDataReq) (resp []*gitlab.Runner, err error) {
var (
runners []*gitlab.Runner
response *gitlab.Response
)
for page := 1; ; page++ {
if runners, response, err = s.gitlab.ListProjectRunners(req.ProjectID, page); err != nil {
return
}
resp = append(resp, runners...)
if response.NextPage == 0 {
break
}
}
return
}
/*-------------------------------------- sync runner ----------------------------------------*/
// SyncAllRunners ...
func (s *Service) SyncAllRunners(projectID int) (totalPage, totalNum int, err error) {
var (
runners []*gitlab.Runner
resp *gitlab.Response
projectInfo *model.ProjectInfo
)
if projectInfo, err = s.dao.ProjectInfoByID(projectID); err != nil {
return
}
for page := 1; ; page++ {
totalPage++
if runners, resp, err = s.gitlab.ListProjectRunners(projectID, page); err != nil {
return
}
for _, runner := range runners {
var (
ipAddress string
)
//ipAddress = runner.IPAddress.String()
runnerDB := &model.StatisticsRunners{
ProjectID: projectID,
ProjectName: projectInfo.Name,
RunnerID: runner.ID,
Description: runner.Description,
Active: runner.Active,
IsShared: runner.IsShared,
IPAddress: ipAddress,
Name: runner.Name,
Online: runner.Online,
Status: runner.Status,
Token: runner.Token,
}
if err = s.SaveDatabaseRunner(runnerDB); err != nil {
log.Error("runner Save Database err: projectID(%d), RunnerID(%d)", projectID, runner.ID)
err = nil
continue
}
totalNum++
}
if resp.NextPage == 0 {
break
}
}
return
}
// SaveDatabaseRunner ...
func (s *Service) SaveDatabaseRunner(runnerDB *model.StatisticsRunners) (err error) {
var total int
if total, err = s.dao.HasRunner(runnerDB.ProjectID, runnerDB.RunnerID); err != nil {
log.Error("SaveDatabaseRunner HasRunner(%+v)", err)
return
}
// found only one, so update
if total == 1 {
if err = s.dao.UpdateRunner(runnerDB.ProjectID, runnerDB.RunnerID, runnerDB); err != nil {
log.Error("SaveDatabaseRunner UpdateRunner(%+v)", err)
return
}
return
} else if total > 1 {
// found repeated row, this situation will not exist under normal
log.Warn("SaveDatabaseRunner commit has more rows(%d)", total)
return
}
// insert row now
if err = s.dao.CreateRunner(runnerDB); err != nil {
log.Error("SaveDatabaseRunner CreateRunner(%+v)", err)
return
}
return
}

View File

@@ -0,0 +1,72 @@
package service
import (
"context"
"go-common/app/admin/ep/saga/conf"
"go-common/app/admin/ep/saga/dao"
"go-common/app/admin/ep/saga/service/gitlab"
"go-common/app/admin/ep/saga/service/wechat"
"github.com/robfig/cron"
)
// Service struct
type Service struct {
dao *dao.Dao
gitlab *gitlab.Gitlab
git *gitlab.Gitlab
cron *cron.Cron
wechat *wechat.Wechat
}
// New a DirService and return.
func New() (s *Service) {
var (
err error
)
s = &Service{
dao: dao.New(),
cron: cron.New(),
}
if err = s.cron.AddFunc(conf.Conf.Property.SyncProject.CheckCron, s.collectprojectproc); err != nil {
panic(err)
}
if err = s.cron.AddFunc(conf.Conf.Property.Git.CheckCron, s.alertProjectPipelineProc); err != nil {
panic(err)
}
if err = s.cron.AddFunc(conf.Conf.Property.SyncData.CheckCron, s.syncdataproc); err != nil {
panic(err)
}
if err = s.cron.AddFunc(conf.Conf.Property.SyncData.CheckCronAll, s.syncalldataproc); err != nil {
panic(err)
}
if err = s.cron.AddFunc(conf.Conf.Property.SyncData.CheckCronWeek, s.syncweekdataproc); err != nil {
panic(err)
}
s.cron.Start()
// init gitlab client
s.gitlab = gitlab.New(conf.Conf.Property.Gitlab.API, conf.Conf.Property.Gitlab.Token)
// init online gitlab client
s.git = gitlab.New(conf.Conf.Property.Git.API, conf.Conf.Property.Git.Token)
// init wechat client
s.wechat = wechat.New(s.dao)
return
}
// Ping check dao health.
func (s *Service) Ping(c context.Context) (err error) {
return s.dao.Ping(c)
}
// Wait wait all closed.
func (s *Service) Wait() {
}
// Close close all dao.
func (s *Service) Close() {
s.dao.Close()
}

View File

@@ -0,0 +1,140 @@
package service
import (
"context"
"flag"
"os"
"testing"
"go-common/app/admin/ep/saga/conf"
"go-common/app/admin/ep/saga/model"
. "github.com/smartystreets/goconvey/convey"
"github.com/xanzy/go-gitlab"
)
var (
srv *Service
)
func TestMain(m *testing.M) {
var err error
flag.Set("conf", "../cmd/saga-admin-test.toml")
if err = conf.Init(); err != nil {
panic(err)
}
srv = New()
os.Exit(m.Run())
}
// TestInsertDB ...
func TestInsertDB(t *testing.T) {
Convey("insertDB", t, func() {
p := &gitlab.Project{
ID: 11,
Name: "test",
Description: "[主站 android java] test",
WebURL: "http://gitlab.bilibili.co/platform/go-common",
SSHURLToRepo: "git@gitlab.bilibili.co:platform/go-common.git",
DefaultBranch: "master",
Namespace: &gitlab.ProjectNamespace{
Name: "mytest",
Kind: "group",
},
}
err := srv.insertDB(p)
So(err, ShouldBeNil)
projectInfo, err := srv.dao.ProjectInfoByID(p.ID)
So(err, ShouldBeNil)
So(projectInfo.ProjectID, ShouldEqual, 11)
So(projectInfo.Name, ShouldEqual, "test")
So(projectInfo.WebURL, ShouldEqual, "http://gitlab.bilibili.co/platform/go-common")
So(projectInfo.Repo, ShouldEqual, "git@gitlab.bilibili.co:platform/go-common.git")
So(projectInfo.DefaultBranch, ShouldEqual, "master")
So(projectInfo.Department, ShouldEqual, "主站")
So(projectInfo.Business, ShouldEqual, "android")
So(projectInfo.Language, ShouldEqual, "java")
So(projectInfo.SpaceName, ShouldEqual, "mytest")
So(projectInfo.SpaceKind, ShouldEqual, "group")
})
}
// TestResolveDes ...
func TestResolveDes(t *testing.T) {
Convey("resolveDes", t, func() {
s := "[主站 android java] test"
department, business, language, parseFail := parseDes(s)
So(department, ShouldEqual, "主站")
So(business, ShouldEqual, "android")
So(language, ShouldEqual, "java")
So(parseFail, ShouldEqual, false)
})
}
// TestCollectProject ...
func TestCollectProject(t *testing.T) {
Convey("CollectProject", t, func() {
err := srv.CollectProject(context.Background())
So(err, ShouldBeNil)
})
}
// TestPushMsg ...
func TestPushMsg(t *testing.T) {
Convey("TestPushMsg", t, func() {
var err error
result := &model.SyncResult{}
for i := 0; i < 10; i++ {
errData := &model.FailData{
ChildID: i,
}
result.FailData = append(result.FailData, errData)
}
err = srv.WechatFailData(model.DataTypeJob, 888, result, nil)
So(err, ShouldBeNil)
})
}
func TestServiceSaveAggregateBranchDatabase(t *testing.T) {
Convey("test service aggregate branch include special char", t, func(ctx C) {
var (
branch = &model.AggregateBranches{
ID: 4,
ProjectID: 666,
ProjectName: "六六大顺",
BranchName: "666",
BranchUserName: "吴维",
BranchMaster: "wuwei",
Behind: 1111,
Ahead: 2222,
LatestSyncTime: nil,
LatestUpdateTime: nil,
IsDeleted: true,
}
//total int
err error
)
Convey("SaveAggregateBranchDatabase", func(ctx C) {
err = srv.SaveAggregateBranchDatabase(context.TODO(), branch)
Convey("Then err should be nil.", func(ctx C) {
So(err, ShouldBeNil)
})
})
})
}

View File

@@ -0,0 +1,308 @@
package service
import (
"context"
"fmt"
"time"
"go-common/app/admin/ep/saga/conf"
"go-common/app/admin/ep/saga/model"
"go-common/library/cache/memcache"
"go-common/library/log"
"github.com/xanzy/go-gitlab"
)
// QueryProject query commit info according to project id.
func (s *Service) QueryProject(c context.Context, object string, req *model.ProjectDataReq) (resp *model.ProjectDataResp, err error) {
var (
data []*model.DataWithTime
queryDes string
total int
)
log.Info("QuerySingleProjectData Type: %d", req.QueryType)
switch req.QueryType {
case model.LastYearPerMonth:
queryDes = model.LastYearPerMonthNote
case model.LastMonthPerDay:
queryDes = model.LastMonthPerDayNote
case model.LastYearPerDay:
queryDes = model.LastYearPerDayNote
default:
log.Warn("QueryProjectCommit Type is not in range")
return
}
queryDes = req.ProjectName + " " + object + queryDes
if data, total, err = s.QueryProjectByTime(req.ProjectID, object, req.QueryType); err != nil {
return
}
resp = &model.ProjectDataResp{
ProjectName: req.ProjectName,
QueryDes: queryDes,
Total: total,
Data: data,
}
return
}
// QueryProjectByTime ...
func (s *Service) QueryProjectByTime(projectID int, object string, queryType int) (resp []*model.DataWithTime, allNum int, err error) {
var (
layout = "2006-01-02"
fmtLayout = `%d-%d-%d 00:00:00`
//response *gitlab.Response
since time.Time
until time.Time
count int
totalItems int
)
year, month, _ := time.Now().Date()
thisMonth := time.Date(year, month, 1, 0, 0, 0, 0, time.Local)
if queryType == model.LastYearPerMonth {
count = model.MonthNumPerYear
} else if queryType == model.LastMonthPerDay {
_, _, count = thisMonth.AddDate(0, 0, -1).Date()
} else if queryType == model.LastYearPerDay {
count = model.DayNumPerYear
}
for i := 1; i <= count; i++ {
if queryType == model.LastYearPerMonth {
since = thisMonth.AddDate(0, -i, 0)
until = thisMonth.AddDate(0, -i+1, 0)
} else if queryType == model.LastMonthPerDay {
since = thisMonth.AddDate(0, 0, -i)
until = thisMonth.AddDate(0, 0, -i+1)
} else if queryType == model.LastYearPerDay {
since = thisMonth.AddDate(0, 0, -i)
until = thisMonth.AddDate(0, 0, -i+1)
}
sinceStr := fmt.Sprintf(fmtLayout, since.Year(), since.Month(), since.Day())
untilStr := fmt.Sprintf(fmtLayout, until.Year(), until.Month(), until.Day())
if object == model.ObjectCommit {
/*if _, response, err = s.gitlab.ListProjectCommit(projectID, 1, &since, &until); err != nil {
return
}*/
if totalItems, err = s.dao.CountCommitByTime(projectID, sinceStr, untilStr); err != nil {
return
}
} else if object == model.ObjectMR {
/*if _, response, err = s.gitlab.ListProjectMergeRequests(projectID, &since, &until, -1); err != nil {
return
}*/
if totalItems, err = s.dao.CountMRByTime(projectID, sinceStr, untilStr); err != nil {
return
}
} else {
log.Warn("QueryProjectByTime object(%s) is not support!", object)
return
}
perData := &model.DataWithTime{
//TotalItem: response.TotalItems,
TotalItem: totalItems,
StartTime: since.Format(layout),
EndTime: until.Format(layout),
}
resp = append(resp, perData)
//allNum = allNum + response.TotalItems
allNum = allNum + totalItems
}
return
}
// QueryTeam ...
func (s *Service) QueryTeam(c context.Context, object string, req *model.TeamDataRequest) (resp *model.TeamDataResp, err error) {
var (
projectInfo []*model.ProjectInfo
reqProject = &model.ProjectInfoRequest{}
dataMap = make(map[string]*model.TeamDataResp)
data []*model.DataWithTime
queryDes string
total int
key string
keyNotExist bool
)
if len(req.Department) <= 0 && len(req.Business) <= 0 {
log.Warn("query department and business are empty!")
return
}
//log.Info("QueryTeamCommit Query Type: %d", req.QueryType)
switch req.QueryType {
case model.LastYearPerMonth:
queryDes = model.LastYearPerMonthNote
case model.LastMonthPerDay:
queryDes = model.LastMonthPerDayNote
case model.LastYearPerDay:
queryDes = model.LastYearPerDayNote
default:
log.Warn("QueryTeamCommit Type is not in range")
return
}
queryDes = req.Department + " " + req.Business + " " + object + queryDes
//get value from mc
key = "saga_admin_" + req.Department + "_" + req.Business + "_" + model.KeyTypeConst[req.QueryType]
if err = s.dao.GetData(c, key, &dataMap); err != nil {
if err == memcache.ErrNotFound {
log.Warn("no such key (%s) in cache, err (%s)", key, err.Error())
keyNotExist = true
} else {
return
}
}
if _, ok := dataMap[object]; !ok {
keyNotExist = true
} else {
resp = dataMap[object]
return
}
log.Info("sync team %s start => type= %d, Department= %s, Business= %s", object, req.QueryType, req.Department, req.Business)
reqProject.Department = req.Department
reqProject.Business = req.Business
if _, projectInfo, err = s.dao.QueryProjectInfo(false, reqProject); err != nil {
return
}
if len(projectInfo) <= 0 {
log.Warn("Found no project!")
return
}
if data, total, err = s.QueryTeamByTime(object, req, req.QueryType, projectInfo); err != nil {
return
}
resp = &model.TeamDataResp{
Department: req.Department,
Business: req.Business,
QueryDes: queryDes,
Total: total,
Data: data,
}
//set value to mc
if keyNotExist {
dataMap[object] = resp
if err = s.dao.SetData(c, key, dataMap); err != nil {
return
}
}
log.Info("sync team %s end", object)
return
}
// QueryTeamByTime query commit info per month of last year and per day of last month according to team.
func (s *Service) QueryTeamByTime(object string, req *model.TeamDataRequest, queryType int, projectInfo []*model.ProjectInfo) (resp []*model.DataWithTime, allNum int, err error) {
var (
layout = "2006-01-02"
response *gitlab.Response
since time.Time
until time.Time
count int
num int
)
year, month, _ := time.Now().Date()
thisMonth := time.Date(year, month, 1, 0, 0, 0, 0, time.Local)
if queryType == model.LastYearPerMonth {
count = model.MonthNumPerYear
} else if queryType == model.LastMonthPerDay {
_, _, count = thisMonth.AddDate(0, 0, -1).Date()
} else if queryType == model.LastYearPerDay {
count = model.DayNumPerYear
}
for i := 1; i <= count; i++ {
if queryType == model.LastYearPerMonth {
since = thisMonth.AddDate(0, -i, 0)
until = thisMonth.AddDate(0, -i+1, 0)
} else if queryType == model.LastMonthPerDay {
since = thisMonth.AddDate(0, 0, -i)
until = thisMonth.AddDate(0, 0, -i+1)
} else if queryType == model.LastYearPerDay {
since = thisMonth.AddDate(0, 0, -i)
until = thisMonth.AddDate(0, 0, -i+1)
}
num = 0
for _, project := range projectInfo {
if object == model.ObjectCommit {
if _, response, err = s.gitlab.ListProjectCommit(project.ProjectID, 1, &since, &until); err != nil {
return
}
} else if object == model.ObjectMR {
if _, response, err = s.gitlab.ListProjectMergeRequests(project.ProjectID, &since, &until, -1); err != nil {
return
}
} else {
log.Warn("QueryTeamByTime object(%s) is not support!", object)
return
}
num = num + response.TotalItems
}
perData := &model.DataWithTime{
TotalItem: num,
StartTime: since.Format(layout),
EndTime: until.Format(layout),
}
resp = append(resp, perData)
allNum = allNum + num
}
return
}
// SyncData ...
func (s *Service) SyncData(c context.Context) (err error) {
log.Info("sync all data info start!")
for _, de := range conf.Conf.Property.DeInfo {
for _, bu := range conf.Conf.Property.BuInfo {
for k, keyType := range model.KeyTypeConst {
key := "saga_admin_" + de.Value + "_" + bu.Value + "_" + keyType
if err = s.dao.DeleteData(c, key); err != nil {
return
}
req := &model.TeamDataRequest{
TeamParam: model.TeamParam{
Department: de.Value,
Business: bu.Value,
},
QueryType: k,
}
if k == 3 {
if _, err = s.QueryTeamPipeline(c, req); err != nil {
return
}
continue
}
if _, err = s.QueryTeamCommit(c, req); err != nil {
return
}
if _, err = s.QueryTeamMr(c, req); err != nil {
return
}
}
}
}
log.Info("sync all data info end!")
return
}

View File

@@ -0,0 +1,405 @@
package service
import (
"context"
"fmt"
"sync"
"go-common/app/admin/ep/saga/conf"
"go-common/app/admin/ep/saga/model"
"go-common/library/log"
)
const _baseBranch = "master"
// syncalldataproc ...
func (s *Service) syncweekdataproc() {
//the blow object will be sync or update all time data
go s.SyncMember()
go s.SyncRunners()
go s.SyncContacts(context.TODO())
}
// syncalldataproc ...
func (s *Service) syncalldataproc() {
if err := s.SyncBranch(); err != nil {
return
}
// AggregateBranch base on branch, if SyncBranch err, so return
s.AggregateBranch()
}
// syncdataproc ...
func (s *Service) syncdataproc() {
var (
wg sync.WaitGroup
done = func(f func() error) {
defer wg.Done()
f()
}
)
wg.Add(2)
go done(s.SyncIssues)
go done(s.SyncPipelines)
wg.Wait()
wg.Add(1)
go done(s.SyncCommit)
wg.Wait()
wg.Add(2)
go done(s.SyncJobs)
go done(s.SyncMR)
wg.Wait()
wg.Add(3)
go done(s.SyncMRNote)
go done(s.SyncMRAwardEmoji)
go done(s.SyncMRDiscussion)
wg.Wait()
s.AggregateMR()
}
// SyncCommit ...
func (s *Service) SyncCommit() (err error) {
var (
projectIDs = conf.Conf.Property.DefaultProject.ProjectIDs
allPage int
allNum int
result *model.SyncResult
)
log.Info("===================== SyncCommit start ========================")
for _, projectID := range projectIDs {
if result, err = s.SyncProjectCommit(projectID); err != nil {
log.Error("SyncCommit projectID(%d), err(%+v)", projectID, err)
go s.WechatFailData(model.DataTypeCommit, projectID, result, err)
return
}
log.Info(">>>>>>>>> SyncCommit projectID(%d) complete Page(%d), Num(%d)", projectID, result.TotalPage, result.TotalNum)
if result != nil && len(result.FailData) > 0 {
go s.WechatFailData(model.DataTypeCommit, projectID, result, nil)
}
allPage = allPage + result.TotalPage
allNum = allNum + result.TotalNum
}
log.Info("===================== SyncCommit finished totalPage(%d), totalNum(%d)========================", allPage, allNum)
return
}
// SyncMR ...
func (s *Service) SyncMR() (err error) {
var (
projectIDs = conf.Conf.Property.DefaultProject.ProjectIDs
allPage int
allNum int
result *model.SyncResult
)
log.Info("===================== SyncMR start ========================")
for _, projectID := range projectIDs {
if result, err = s.SyncProjectMR(context.TODO(), projectID); err != nil {
log.Error("SyncMR projectID(%d), err(%+v)", projectID, err)
go s.WechatFailData(model.DataTypeMR, projectID, result, err)
return
}
log.Info(">>>>>>>>> SyncMR projectID(%d) complete Page(%d), Num(%d)", projectID, result.TotalPage, result.TotalNum)
if result != nil && len(result.FailData) > 0 {
go s.WechatFailData(model.DataTypeMR, projectID, result, nil)
}
allPage = allPage + result.TotalPage
allNum = allNum + result.TotalNum
}
log.Info("===================== SyncMR finished totalPage(%d), totalNum(%d)========================", allPage, allNum)
return
}
// AggregateMR ...
func (s *Service) AggregateMR() (err error) {
var (
projectIDs = conf.Conf.Property.DefaultProject.ProjectIDs
)
log.Info("===================== AggMR start ========================")
for _, projectID := range projectIDs {
if err = s.AggregateProjectMR(context.TODO(), projectID); err != nil {
log.Error("AggMR projectID(%d), err(%+v)", projectID, err)
return
}
log.Info(">>>>>>>>> AggMR projectID(%d) complete", projectID)
}
log.Info("===================== AggMR finished ========================")
return
}
// SyncJobs ...
func (s *Service) SyncJobs() (err error) {
var (
projectIDs = conf.Conf.Property.DefaultProject.ProjectIDs
allPage int
allNum int
result *model.SyncResult
)
log.Info("===================== SyncJobs start ========================")
for _, projectID := range projectIDs {
if result, err = s.SyncProjectJobs(projectID); err != nil {
log.Error("SyncJobs projectID(%d), err(%+v)", projectID, err)
go s.WechatFailData(model.DataTypeJob, projectID, result, err)
return
}
log.Info(">>>>>>>>> SyncJobs projectID(%d) complete Page(%d), Num(%d)", projectID, result.TotalPage, result.TotalNum)
if result != nil && len(result.FailData) > 0 {
go s.WechatFailData(model.DataTypeJob, projectID, result, nil)
}
allPage = allPage + result.TotalPage
allNum = allNum + result.TotalNum
}
log.Info("===================== SyncJobs finished totalPage(%d), totalNum(%d)========================", allPage, allNum)
return
}
// SyncPipelines ...
func (s *Service) SyncPipelines() (err error) {
var (
projectIDs = conf.Conf.Property.DefaultProject.ProjectIDs
allPage int
allNum int
result *model.SyncResult
)
log.Info("===================== SyncPipelines start ========================")
for _, projectID := range projectIDs {
if result, err = s.SyncProjectPipelines(projectID); err != nil {
log.Error("SyncPipelines projectID(%d), err(%+v)", projectID, err)
go s.WechatFailData(model.DataTypePipeline, projectID, result, err)
return
}
log.Info(">>>>>>>>> SyncPipelines projectID(%d) complete Page(%d), Num(%d)", projectID, result.TotalPage, result.TotalNum)
if result != nil && len(result.FailData) > 0 {
go s.WechatFailData(model.DataTypePipeline, projectID, result, nil)
}
allPage = allPage + result.TotalPage
allNum = allNum + result.TotalNum
}
log.Info("===================== SyncPipelines finished totalPage(%d), totalNum(%d)========================", allPage, allNum)
return
}
// SyncMRNote ...
func (s *Service) SyncMRNote() (err error) {
var (
projectIDs = conf.Conf.Property.DefaultProject.ProjectIDs
totalPage int
totalNum int
page int
num int
)
log.Info("===================== SyncNote start ========================")
for _, projectID := range projectIDs {
if page, num, err = s.SyncProjectNotes(context.TODO(), projectID); err != nil {
log.Error("SyncNotes projectID(%d), err(%+v)", projectID, err)
return
}
log.Info(">>>>>>>>> SyncNote projectID(%d) complete Page(%d), Num(%d)", projectID, page, num)
totalPage = totalPage + page
totalNum = totalNum + num
}
log.Info("===================== SyncNote finished totalPage(%d), totalNum(%d)========================", totalPage, totalNum)
return
}
// SyncMember ...
func (s *Service) SyncMember() (err error) {
var (
projectIDs = conf.Conf.Property.DefaultProject.ProjectIDs
totalPage int
totalNum int
page int
num int
)
log.Info("===================== SyncMember start ========================")
for _, projectID := range projectIDs {
if page, num, err = s.SyncProjectMember(context.TODO(), projectID); err != nil {
log.Error("SyncMember projectID(%d), err(%+v)", projectID, err)
return
}
log.Info(">>>>>>>>> SyncMember projectID(%d) complete Page(%d), Num(%d)", projectID, page, num)
totalPage = totalPage + page
totalNum = totalNum + num
}
log.Info("===================== SyncMember finished totalPage(%d), totalNum(%d)========================", totalPage, totalNum)
return
}
// SyncMRAwardEmoji ...
func (s *Service) SyncMRAwardEmoji() (err error) {
var (
projectIDs = conf.Conf.Property.DefaultProject.ProjectIDs
totalPage int
totalNum int
page int
num int
)
log.Info("===================== SyncMRAwardEmoji start ========================")
for _, projectID := range projectIDs {
if page, num, err = s.SyncProjectAwardEmoji(context.TODO(), projectID); err != nil {
log.Error("SyncMRAwardEmoji projectID(%d), err(%+v)", projectID, err)
return
}
log.Info(">>>>>>>>> SyncMRAwardEmoji projectID(%d) complete Page(%d), Num(%d)", projectID, page, num)
totalPage = totalPage + page
totalNum = totalNum + num
}
log.Info("===================== SyncMRAwardEmoji finished totalPage(%d), totalNum(%d)========================", totalPage, totalNum)
return
}
// SyncMRDiscussion ...
func (s *Service) SyncMRDiscussion() (err error) {
var (
projectIDs = conf.Conf.Property.DefaultProject.ProjectIDs
totalPage int
totalNum int
page int
num int
)
log.Info("===================== SyncMRDiscussion start ========================")
for _, projectID := range projectIDs {
if page, num, err = s.SyncProjectDiscussion(context.TODO(), projectID); err != nil {
log.Error("SyncMRDiscussion projectID(%d), err(%+v)", projectID, err)
return
}
log.Info(">>>>>>>>> SyncMRDiscussion projectID(%d) complete Page(%d), Num(%d)", projectID, page, num)
totalPage = totalPage + page
totalNum = totalNum + num
}
log.Info("===================== SyncMRDiscussion finished totalPage(%d), totalNum(%d)========================", totalPage, totalNum)
return
}
// SyncRunners ...
func (s *Service) SyncRunners() (err error) {
var (
projectIDs = conf.Conf.Property.DefaultProject.ProjectIDs
totalPage int
totalNum int
page int
num int
)
log.Info("===================== SyncRunners start ========================")
for _, projectID := range projectIDs {
if page, num, err = s.SyncAllRunners(projectID); err != nil {
log.Error("SyncRunners projectID(%d), err(%+v)", projectID, err)
return
}
log.Info(">>>>>>>>> SyncRunners projectID(%d) complete Page(%d), Num(%d)", projectID, page, num)
totalPage = totalPage + page
totalNum = totalNum + num
}
log.Info("===================== SyncRunners finished totalPage(%d), totalNum(%d)========================", totalPage, totalNum)
return
}
// SyncIssues ...
func (s *Service) SyncIssues() (err error) {
var (
projectIDs = conf.Conf.Property.DefaultProject.ProjectIDs
totalPage int
totalNum int
page int
num int
)
log.Info("===================== SyncIssues start ========================")
for _, projectID := range projectIDs {
if page, num, err = s.SyncAllIssues(projectID); err != nil {
log.Error("SyncIssues projectID(%d), err(%+v)", projectID, err)
return
}
log.Info(">>>>>>>>> SyncIssues projectID(%d) complete Page(%d), Num(%d)", projectID, page, num)
totalPage = totalPage + page
totalNum = totalNum + num
}
log.Info("===================== SyncIssues finished totalPage(%d), totalNum(%d)========================", totalPage, totalNum)
return
}
// SyncBranch ...
func (s *Service) SyncBranch() (err error) {
var (
projectIDs = conf.Conf.Property.DefaultProject.ProjectIDs
allPage int
allNum int
result *model.SyncResult
)
log.Info("===================== SyncBranch start ========================")
for _, projectID := range projectIDs {
if result, err = s.SyncProjectBranch(context.TODO(), projectID); err != nil {
log.Error("SyncBranch projectID(%d), err(%+v)", projectID, err)
go s.WechatFailData(model.DataTypeBranch, projectID, result, err)
return
}
log.Info(">>>>>>>>> SyncBranch projectID(%d) complete Page(%d), Num(%d)", projectID, result.TotalPage, result.TotalNum)
if result != nil && len(result.FailData) > 0 {
go s.WechatFailData(model.DataTypeBranch, projectID, result, nil)
}
allPage = allPage + result.TotalPage
allNum = allNum + result.TotalNum
}
log.Info("===================== SyncBranch finished totalPage(%d), totalNum(%d)========================", allPage, allNum)
return
}
// AggregateBranch ...
func (s *Service) AggregateBranch() (err error) {
var projectIDs = conf.Conf.Property.DefaultProject.ProjectIDs
log.Info("===================== SyncAggregateBranch start ========================")
for _, projectID := range projectIDs {
if err = s.AggregateProjectBranch(context.TODO(), projectID, _baseBranch); err != nil {
log.Error("SyncAggregateBranch projectID(%d), err(%+v)", projectID, err)
return
}
}
log.Info("===================== SyncAggregateBranch finished ========================")
return
}
// WechatFailData ...
func (s *Service) WechatFailData(dataType string, projectID int, result *model.SyncResult, error error) (err error) {
var (
ctx = context.Background()
users = conf.Conf.Property.SyncData.WechatUser
content string
)
title := "[SAGA-ADMIN] SYNC ERROR !!!"
subject := fmt.Sprintf("%s\n\nDataType ( %s )\nProjectID ( %d )", title, dataType, projectID)
if error != nil {
return s.wechat.PushMsg(ctx, users, fmt.Sprintf("%s\n\n%s", subject, error.Error()))
}
if result == nil || len(result.FailData) <= 0 {
return
}
content = "LIST : \n"
for _, data := range result.FailData {
if dataType == model.DataTypeJob || dataType == model.DataTypePipeline || dataType == model.DataTypeMR {
content += fmt.Sprintf("%d, ", data.ChildID)
} else if dataType == model.DataTypeCommit || dataType == model.DataTypeBranch {
content += fmt.Sprintf("%s\n", data.ChildIDStr)
}
}
return s.wechat.PushMsg(ctx, users, fmt.Sprintf("%s\n\n%s", subject, content))
}

View File

@@ -0,0 +1,28 @@
package service
import (
"context"
"go-common/app/admin/ep/saga/model"
)
// MergeTasks query all tasks for the project.
func (s *Service) MergeTasks(c context.Context, req *model.TasksReq) (resp *model.TasksResp, err error) {
var (
tasks []*model.Task
status int
)
resp = new(model.TasksResp)
if _, err = s.dao.ProjectInfoByID(req.ProjID); err != nil {
return
}
for _, status = range req.Statuses {
if tasks, err = s.dao.Tasks(req.ProjID, status); err != nil {
return
}
resp.Tasks = append(resp.Tasks, tasks...)
}
return
}

View File

@@ -0,0 +1,13 @@
package service
import "go-common/app/admin/ep/saga/model"
// UserInfo get username and email.
func (s *Service) UserInfo(userName string) (userInfo *model.User) {
userInfo = &model.User{
Name: userName,
EMail: userName + "@bilibili.com",
}
return
}

View File

@@ -0,0 +1,29 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["utils.go"],
importpath = "go-common/app/admin/ep/saga/service/utils",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = ["//app/admin/ep/saga/conf: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,119 @@
package utils
import (
"bytes"
"sort"
"strconv"
"time"
"go-common/app/admin/ep/saga/conf"
)
// CalAverageTime 计算时间的平均/*分位 时间
func CalAverageTime(timeType int, timeArray []float64) (result float64) {
sort.Float64s(timeArray)
if timeType == 0 {
// 判断数量为零
if len(timeArray) == 0 {
result = 0
} else {
var sum float64
for _, t := range timeArray {
sum += t
}
result = sum / float64(len(timeArray))
}
} else if timeType > 0 && timeType < 11 {
if len(timeArray) == 0 {
result = 0
} else {
index := len(timeArray) * timeType / 10
result = timeArray[index]
}
}
return
}
// CalSizeTime ...
func CalSizeTime(time, max, min float64) (float64, float64) {
if max == 0 {
max = time
return max, min
}
if min == 0 {
min = time
return max, min
}
if time > max {
max = time
return max, min
}
if time != 0 && time < min {
min = time
return max, min
}
return max, min
}
// CalSyncTime ...
func CalSyncTime() (since, until *time.Time) {
syncDays := conf.Conf.Property.SyncData.DefaultSyncDays
year, month, day := time.Now().Date()
untilTime := time.Date(year, month, day, 0, 0, 0, 0, time.Local)
sinceTime := time.Date(year, month, day-syncDays, 0, 0, 0, 0, time.Local)
since = &sinceTime
until = &untilTime
return
}
// CombineSlice ...
func CombineSlice(s1, s2 []float64) []float64 {
slice := make([]float64, len(s1)+len(s2))
copy(slice, s1)
copy(slice[len(s1):], s2)
return slice
}
// InSlice ...
func InSlice(key interface{}, list []string) bool {
for _, item := range list {
if key == item {
return true
}
}
return false
}
// Unicode2Chinese ...
func Unicode2Chinese(str string) string {
buf := bytes.NewBuffer(nil)
i, j := 0, len(str)
for i < j {
x := i + 6
if x > j {
buf.WriteString(str[i:])
break
}
if str[i] == '\\' && str[i+1] == 'u' {
hex := str[i+2 : x]
r, err := strconv.ParseUint(hex, 16, 64)
if err == nil {
buf.WriteRune(rune(r))
} else {
buf.WriteString(str[i:x])
}
i = x
} else {
buf.WriteByte(str[i])
i++
}
}
return buf.String()
}

View File

@@ -0,0 +1,327 @@
package service
import (
"context"
"fmt"
"net/url"
"go-common/app/admin/ep/saga/conf"
"go-common/app/admin/ep/saga/model"
"go-common/app/admin/ep/saga/service/wechat"
"go-common/library/log"
"github.com/pkg/errors"
)
const qyWechatURL = "https://qyapi.weixin.qq.com"
// CollectWachatUsers send required wechat visible users stored in memcache by email
func (s *Service) CollectWachatUsers(c context.Context) (err error) {
var (
contactInfo *model.ContactInfo
userMap = make(map[string]model.RequireVisibleUser)
user string
)
if err = s.dao.RequireVisibleUsersRedis(c, &userMap); err != nil {
log.Error("get require visible user error(%v)", err)
return
}
for k, v := range userMap {
if contactInfo, err = s.dao.QueryUserByID(k); err == nil {
if contactInfo.VisibleSaga {
continue
}
}
user += v.UserName + " , " + v.NickName + "\n"
}
/*content := fmt.Sprintf("\n\n邮箱前缀 昵称\n\n%s", user)
for _, addr := range conf.Conf.Property.ReportRequiredVisible.AlertAddrs {
if err = mail.SendMail2(addr, "需添加的企业微信名单", content); err != nil {
return
}
}*/
if err = s.dao.DeleteRequireVisibleUsersRedis(c); err != nil {
log.Error("Delete require visible user error(%v)", err)
return
}
return
}
// SyncContacts sync the wechat contacts 更新企业微信列表用户信息和saga信息
func (s *Service) SyncContacts(c context.Context) (err error) {
var (
w = wechat.New(s.dao)
)
if err = w.SyncContacts(c); err != nil {
return
}
return
}
// QueryContacts query machine logs.
func (s *Service) QueryContacts(c context.Context, queryRequest *model.Pagination) (p *model.PaginateContact, err error) {
var (
total int64
contacts []*model.ContactInfo
)
fmt.Print(queryRequest.PageNum)
if total, contacts, err = s.dao.FindContacts(queryRequest.PageNum, queryRequest.PageSize); err != nil {
return
}
fmt.Print(queryRequest.PageNum)
p = &model.PaginateContact{
PageNum: queryRequest.PageNum,
PageSize: queryRequest.PageSize,
Total: total,
Contacts: contacts,
}
return
}
// QueryContactLogs query contact logs.
func (s *Service) QueryContactLogs(c context.Context, queryRequest *model.QueryContactLogRequest) (p *model.PaginateContactLog, err error) {
var (
total int64
machineLogs []*model.AboundContactLog
)
if total, machineLogs, err = s.dao.FindMachineLogs(queryRequest); err != nil {
return
}
p = &model.PaginateContactLog{
PageNum: queryRequest.PageNum,
PageSize: queryRequest.PageSize,
Total: total,
MachineLogs: machineLogs,
}
return
}
// Wechat ...
func (s *Service) Wechat() *wechat.Wechat {
return wechat.New(s.dao)
}
// CreateWechat ...
func (s *Service) CreateWechat(c context.Context, req *model.CreateChatReq, username string) (resp *model.CreateChatResp, err error) {
var (
token string
userIDs []string
ownerInfo *model.ContactInfo
w = wechat.New(s.dao)
)
u := qyWechatURL + "/cgi-bin/appchat/create"
params := url.Values{}
wechatInfo := &model.WechatCreateLog{
Name: req.Name,
Owner: req.Owner,
ChatID: req.ChatID,
Cuser: username,
Status: 1,
}
//获取企业token
if token, err = w.AccessToken(c, conf.Conf.Property.Wechat); err != nil {
return
}
params.Set("access_token", token)
//get owner and users id
if ownerInfo, err = s.dao.QueryUserByUserName(req.Owner); err != nil {
return
}
if userIDs, err = s.QueryUserIds(req.UserList); err != nil {
return
}
req.Owner = ownerInfo.UserID
req.UserList = userIDs
if err = s.dao.PostJSON(c, u, "", params, &resp, req); err != nil {
return
}
//add create wechat info to database
if err = s.dao.AddWechatCreateLog(wechatInfo); err != nil {
return
}
resp = &model.CreateChatResp{
ChatID: wechatInfo.ChatID,
}
return
}
// QueryUserIds ...
func (s *Service) QueryUserIds(userNames []string) (userIds []string, err error) {
var (
userName string
contactInfo *model.ContactInfo
)
if len(userNames) == 0 {
err = errors.Errorf("UserIds: userNames is empty!")
return
}
for _, userName = range userNames {
if contactInfo, err = s.dao.QueryUserByUserName(userName); err != nil {
err = errors.Wrapf(err, "UserIds: no such user (%s) in db, err (%s)", userName, err.Error())
return
}
log.Info("UserIds: username (%s), userid (%s)", userName, contactInfo.UserID)
if contactInfo.UserID != "" {
userIds = append(userIds, contactInfo.UserID)
}
}
return
}
// QueryWechatCreateLog ...
func (s *Service) QueryWechatCreateLog(c context.Context, req *model.Pagination, username string) (resp *model.CreateChatLogResp, err error) {
var (
logs []*model.WechatCreateLog
logsResp []*model.CreateChatLog
total int
wechatCreateInfo *model.WechatCreateLog
)
if logs, total, err = s.dao.QueryWechatCreateLog(true, req, wechatCreateInfo); err != nil {
return
}
for _, log := range logs {
createChatlog := &model.CreateChatLog{}
if log.Cuser == username {
createChatlog.Buttons = append(createChatlog.Buttons, "WECHAT_TEST")
}
createChatlog.WechatCreateLog = log
logsResp = append(logsResp, createChatlog)
}
resp = &model.CreateChatLogResp{
Total: total,
Pagination: req,
Logs: logsResp,
}
return
}
// WechatParams ...
func (s *Service) WechatParams(c context.Context, chatid string) (resp *model.GetChatResp, err error) {
var (
w = wechat.New(s.dao)
token string
)
if token, err = w.AccessToken(c, conf.Conf.Property.Wechat); err != nil {
return
}
u := qyWechatURL + "/cgi-bin/appchat/get"
params := url.Values{}
params.Set("access_token", token)
params.Set("chatid", chatid)
err = s.dao.WechatParams(c, u, params, &resp)
return
}
// SendGroupWechat ...
func (s *Service) SendGroupWechat(c context.Context, req *model.SendChatReq) (resp *model.ChatResp, err error) {
var (
token string
w = wechat.New(s.dao)
total int
getChatResp *model.GetChatResp
owner string
contentDB = req.Text.Content
)
u := qyWechatURL + "/cgi-bin/appchat/send"
params := url.Values{}
if token, err = w.AccessToken(c, conf.Conf.Property.Wechat); err != nil {
return
}
params.Set("access_token", token)
if err = s.dao.PostJSON(c, u, "", params, &resp, req); err != nil {
return
}
if len(contentDB) > model.MaxWechatLen {
contentDB = contentDB[:model.MaxWechatLen]
}
chatLog := &model.WechatChatLog{
ChatID: req.ChatID,
MsgType: req.MsgType,
Content: contentDB,
Safe: req.Safe,
Status: 1,
}
if err = s.dao.CreateChatLog(chatLog); err != nil {
return
}
info := &model.WechatCreateLog{
ChatID: req.ChatID,
}
if _, total, err = s.dao.QueryWechatCreateLog(false, nil, info); err != nil {
return
}
if total == 0 {
getChatResp, _ = s.WechatParams(c, req.ChatID)
owner = getChatResp.ChatInfo.Owner
contactInfo, _ := s.dao.QueryUserByID(owner)
wechatInfo := &model.WechatCreateLog{
Name: getChatResp.ChatInfo.Name,
Owner: contactInfo.UserName,
ChatID: req.ChatID,
Status: 2,
}
if err = s.dao.AddWechatCreateLog(wechatInfo); err != nil {
return
}
}
return
}
// SendWechat ...
func (s *Service) SendWechat(c context.Context, req *model.SendMessageReq) (resp *model.ChatResp, err error) {
var (
w = wechat.New(s.dao)
)
err = w.PushMsg(c, req.Touser, req.Content)
return
}
// UpdateWechat ...
func (s *Service) UpdateWechat(c context.Context, req *model.UpdateChatReq) (resp *model.ChatResp, err error) {
var (
token string
w = wechat.New(s.dao)
)
u := qyWechatURL + "/cgi-bin/appchat/update"
params := url.Values{}
if token, err = w.AccessToken(c, conf.Conf.Property.Wechat); err != nil {
return
}
params.Set("access_token", token)
if err = s.dao.PostJSON(c, u, "", params, &resp, req); err != nil {
return
}
return
}
// SyncWechatContacts ...
func (s *Service) SyncWechatContacts(c context.Context) (message string, err error) {
var (
w = wechat.New(s.dao)
)
if err = w.AnalysisContacts(c); err != nil {
return
}
message = "同步完成"
return
}

View File

@@ -0,0 +1,53 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["wechat_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/admin/ep/saga/conf:go_default_library",
"//app/admin/ep/saga/dao:go_default_library",
"//app/admin/ep/saga/model:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"contact.go",
"wechat.go",
],
importpath = "go-common/app/admin/ep/saga/service/wechat",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/admin/ep/saga/conf:go_default_library",
"//app/admin/ep/saga/dao:go_default_library",
"//app/admin/ep/saga/model:go_default_library",
"//library/log: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,189 @@
package wechat
import (
"context"
"go-common/app/admin/ep/saga/model"
"go-common/library/log"
)
// Changes changes structure
type Changes struct {
Adds []*model.ContactInfo
Upts []*model.ContactInfo
Dels []*model.ContactInfo
}
// SyncContacts sync the contacts from wechat work 更新用户信息列表
func (w *Wechat) SyncContacts(c context.Context) (err error) {
//更新数据库用户列表 contact_infos
if err = w.AnalysisContacts(c); err != nil {
return
}
//更新用户saga信息
/*if err = w.UpdateVisible(c); err != nil {
return
}*/
return
}
// AnalysisContacts analysis the contact difference and save them 从企业微信更新用户列表最新
func (w *Wechat) AnalysisContacts(c context.Context) (err error) {
var (
contactsInDB []*model.ContactInfo
wechatContacts []*model.ContactInfo
changes = &Changes{}
)
//数据库里查询用户信息列表
if contactsInDB, err = w.dao.ContactInfos(); err != nil {
return
}
//企业微信接口查询用户列表
if wechatContacts, err = w.QueryWechatContacts(c); err != nil {
return
}
if changes, err = w.diffChanges(wechatContacts, contactsInDB); err != nil {
return
}
//saveChanges 更新用户信息
if err = w.saveChanges(changes); err != nil {
return
}
return
}
// QueryWechatContacts query wechat contacts with access token 获取用户信息列表
func (w *Wechat) QueryWechatContacts(c context.Context) (contacts []*model.ContactInfo, err error) {
var (
token string
)
if token, err = w.AccessToken(c, w.contact); err != nil {
return
}
if contacts, err = w.dao.WechatContacts(c, token); err != nil {
return
}
return
}
//saveChanges 保存企业微信与数据库用户信息的更新内容
func (w *Wechat) saveChanges(changes *Changes) (err error) {
var (
contact *model.ContactInfo
)
log.Info("saveChanges add(%d), upt(%d), del(%d)", len(changes.Adds), len(changes.Upts), len(changes.Dels))
for _, contact = range changes.Adds {
if err = w.dao.CreateContact(contact); err != nil {
return
}
log.Info("saveChanges add: %v", contact)
}
for _, contact = range changes.Upts {
if err = w.dao.UptContact(contact); err != nil {
return
}
log.Info("saveChanges upt: %v", contact)
}
for _, contact = range changes.Dels {
if err = w.dao.DelContact(contact); err != nil {
return
}
log.Info("saveChanges del: %v", contact)
}
return
}
//diffChanges 对比企业微信用户列表和数据库用户列表
func (w *Wechat) diffChanges(wechatContacts, contactsInDB []*model.ContactInfo) (changes *Changes, err error) {
var (
contact *model.ContactInfo
wechatContactsMap = make(map[string]*model.ContactInfo)
contactsInDBMap = make(map[string]*model.ContactInfo)
wechatContactIDs []string
dbContactsIDs []string
userID string
)
changes = new(Changes)
for _, contact = range wechatContacts {
wechatContactsMap[contact.UserID] = contact
wechatContactIDs = append(wechatContactIDs, contact.UserID)
}
for _, contact = range contactsInDB {
contactsInDBMap[contact.UserID] = contact
dbContactsIDs = append(dbContactsIDs, contact.UserID)
}
// 分析变化
for _, userID = range wechatContactIDs {
contact = wechatContactsMap[userID]
if w.inSlice(dbContactsIDs, userID) { // 企业微信联系人ID在数据库中能找到
if !contact.AlmostEqual(contactsInDBMap[userID]) { // 但是域不同
contact.ID = contactsInDBMap[userID].ID
changes.Upts = append(changes.Upts, contact)
}
} else {
changes.Adds = append(changes.Adds, contact) // 这个联系人是新增的
}
}
for _, userID = range dbContactsIDs {
if !w.inSlice(wechatContactIDs, userID) {
changes.Dels = append(changes.Dels, contactsInDBMap[userID])
}
}
return
}
func (w *Wechat) inSlice(slice []string, target string) bool {
for _, v := range slice {
if v == target {
return true
}
}
return false
}
// UpdateVisible update the visible property 更新用户saga信息
func (w *Wechat) UpdateVisible(c context.Context) (err error) {
var (
user *model.UserInfo
users []*model.UserInfo
contact *model.ContactInfo
)
//获取用户ID列表
if users, err = w.querySagaVisible(c); err != nil {
return
}
for _, user = range users {
//定义用户信息--saga是否可使用
contact = &model.ContactInfo{UserID: user.UserID, VisibleSaga: true}
if err = w.dao.UptContact(contact); err != nil {
return
}
}
return
}
//querySagaVisible 获取用户ID列表
func (w *Wechat) querySagaVisible(c context.Context) (users []*model.UserInfo, err error) {
var (
token string
)
if token, err = w.AccessToken(c, w.saga); err != nil {
return
}
if users, err = w.dao.WechatSagaVisible(c, token, w.saga.AppID); err != nil {
return
}
return
}

View File

@@ -0,0 +1,182 @@
package wechat
import (
"context"
"encoding/json"
"fmt"
"strings"
"go-common/app/admin/ep/saga/conf"
"go-common/app/admin/ep/saga/dao"
"go-common/app/admin/ep/saga/model"
"go-common/library/log"
"github.com/pkg/errors"
)
// Wechat 企业微信应用
type Wechat struct {
dao *dao.Dao
saga *model.AppConfig
contact *model.AppConfig
}
// New create an new wechat work
func New(d *dao.Dao) (w *Wechat) {
w = &Wechat{
dao: d,
saga: conf.Conf.Property.Wechat,
contact: conf.Conf.Property.Contact,
}
return w
}
// NewTxtNotify create wechat format text notification 从配置初始化企业微信TxtNotification
func (w *Wechat) NewTxtNotify(content string) (txtMsg *model.TxtNotification) {
return &model.TxtNotification{
Notification: model.Notification{
MsgType: "text",
AgentID: w.saga.AppID,
},
Body: model.Text{
Content: content,
},
Safe: 0,
}
}
// AccessToken get access_token from cache first, if not found, get it via wechat api.
func (w *Wechat) AccessToken(c context.Context, app *model.AppConfig) (token string, err error) {
var (
key string
expire int32
)
key = fmt.Sprintf("appid_%d", app.AppID)
if token, err = w.dao.AccessTokenRedis(c, key); err != nil {
log.Warn("AccessToken: failed to get access_token from cache, appId (%s), error (%s)", app.AppID, err.Error())
//企业微信api获取公司token
if token, expire, err = w.dao.WechatAccessToken(c, app.AppSecret); err != nil {
err = errors.Wrapf(err, "AccessToken: both mc and api can't provide access_token, appId(%s)", app.AppID)
return
}
// 通过API获取到了缓存一波
err = w.dao.SetAccessTokenRedis(c, key, token, expire)
return
}
if token == "" {
if token, expire, err = w.dao.WechatAccessToken(c, app.AppSecret); err != nil {
return
}
// 通过API获取到了缓存一波
err = w.dao.SetAccessTokenRedis(c, key, token, expire)
}
return
}
// PushMsg push text message via wechat notification api with access_token.推送企业微信
func (w *Wechat) PushMsg(c context.Context, userNames []string, content string) (err error) {
var (
token string
userIds string
invalidUser string
userNamesByte []byte
txtMsg = w.NewTxtNotify(content)
contentDB = content
)
//获取企业token
if token, err = w.AccessToken(c, w.saga); err != nil {
return
}
if token == "" {
err = errors.Errorf("PushMsg: get access token failed, it's empty. appid (%s), secret (%s)", w.saga.AppID, w.saga.AppSecret)
return
}
//员工编号以竖线分隔
if userIds, err = w.UserIds(userNames); err != nil {
return
}
txtMsg.ToUser = userIds
if invalidUser, err = w.dao.WechatPushMsg(c, token, txtMsg); err != nil {
if err = w.addRequireVisible(c, invalidUser); err != nil {
log.Error("PushMsg add userID (%s) in cache, error(%s)", invalidUser, err.Error())
}
return
}
if userNamesByte, err = json.Marshal(userNames); err != nil {
return
}
if len(contentDB) > model.MaxWechatLen {
contentDB = contentDB[:model.MaxWechatLen]
}
messageLog := &model.WechatMessageLog{
Touser: string(userNamesByte),
Content: contentDB,
Status: 1,
}
return w.dao.CreateMessageLog(messageLog)
}
// UserIds query user ids for user name list 查询员工编号
func (w *Wechat) UserIds(userNames []string) (ids string, err error) {
if ids, err = w.dao.UserIds(userNames); err != nil {
return
}
return
}
// addRequireVisible update wechat require visible users in memcache
func (w *Wechat) addRequireVisible(c context.Context, userIDs string) (err error) {
var (
contactInfo *model.ContactInfo
userID string
alreadyIn bool
)
users := strings.Split(userIDs, "|")
for _, userID = range users {
//查看是否缓存,缓存则继续
if alreadyIn, err = w.alreadyInCache(c, userID); err != nil || alreadyIn {
continue
}
//未缓存从数据库查询
if contactInfo, err = w.dao.QueryUserByID(userID); err != nil {
log.Error("no such userID (%s) in db, error(%s)", userID, err.Error())
return
}
//数据库查询结果缓存
if err = w.dao.SetRequireVisibleUsersRedis(c, contactInfo); err != nil {
log.Error("failed set to cache userID (%s) username (%s), err (%s)", userID, contactInfo.UserName, err.Error())
return
}
}
return
}
// alreadyInCache check user is or not in the memcache
func (w *Wechat) alreadyInCache(c context.Context, userID string) (alreadyIn bool, err error) {
var (
userMap = make(map[string]model.RequireVisibleUser)
)
//查询所有的值
if err = w.dao.RequireVisibleUsersRedis(c, &userMap); err != nil {
log.Error("get userID (%s) from cache error(%s)", userID, err.Error())
return
}
//匹配需要查询的用户id
for k, v := range userMap {
if userID == k {
log.Info("(%s) is already exist in cache, value(%v)", k, v)
alreadyIn = true
return
}
}
return
}

View File

@@ -0,0 +1,143 @@
package wechat
import (
"context"
"flag"
"os"
"testing"
"go-common/app/admin/ep/saga/conf"
"go-common/app/admin/ep/saga/dao"
"go-common/app/admin/ep/saga/model"
. "github.com/smartystreets/goconvey/convey"
)
var (
mydao *dao.Dao
wechat *Wechat
ctx = context.Background()
)
func TestMain(m *testing.M) {
var err error
flag.Set("conf", "../../cmd/saga-admin-test.toml")
if err = conf.Init(); err != nil {
panic(err)
}
mydao = dao.New()
defer mydao.Close()
wechat = New(mydao)
os.Exit(m.Run())
}
func TestAddRequireVisible(t *testing.T) {
var (
err error
userMap = make(map[string]model.RequireVisibleUser)
)
Convey("TEST addRequireVisible", t, func() {
err = wechat.addRequireVisible(ctx, "000000")
So(err, ShouldNotBeNil)
err = wechat.addRequireVisible(ctx, "001134")
So(err, ShouldBeNil)
err = mydao.RequireVisibleUsersRedis(ctx, &userMap)
So(err, ShouldBeNil)
So(userMap, ShouldContainKey, "001134")
})
}
func TestAlreadyInCache(t *testing.T) {
var (
err error
result bool
contactInfo model.ContactInfo
)
contactInfo = model.ContactInfo{
ID: "111",
UserName: "zhangsan",
UserID: "222",
NickName: "xiaolizi",
VisibleSaga: true,
}
Convey("TEST alreadyInCache", t, func() {
result, err = wechat.alreadyInCache(ctx, "000")
So(err, ShouldBeNil)
So(result, ShouldEqual, false)
So(mydao.SetRequireVisibleUsersRedis(ctx, &contactInfo), ShouldBeNil)
result, err = wechat.alreadyInCache(ctx, "222")
So(err, ShouldBeNil)
So(result, ShouldEqual, true)
})
}
func TestSyncContacts(t *testing.T) {
var (
err error
contactInfo = &model.ContactInfo{
UserID: "E10021",
UserName: "eyotang",
NickName: "ben大神点C",
}
modify = &model.ContactInfo{
UserID: "000328",
UserName: "eyotang",
NickName: "ben大神点C",
VisibleSaga: false,
}
target *model.ContactInfo
almostEqual bool
)
Convey("TEST sync after add incorrect", t, func() {
err = wechat.dao.CreateContact(contactInfo)
So(err, ShouldBeNil)
target, err = wechat.dao.QueryUserByID(contactInfo.UserID)
So(err, ShouldBeNil)
almostEqual = contactInfo.AlmostEqual(target)
So(almostEqual, ShouldBeTrue)
err = wechat.SyncContacts(ctx)
So(err, ShouldBeNil)
target, err = wechat.dao.QueryUserByID(contactInfo.UserID)
So(err, ShouldNotBeNil)
})
Convey("TEST aync after change", t, func() {
contactInfo, err = wechat.dao.QueryUserByID(modify.UserID)
So(err, ShouldBeNil)
modify.ID = contactInfo.ID
err = wechat.dao.UptContact(contactInfo)
So(err, ShouldBeNil)
err = wechat.SyncContacts(ctx)
So(err, ShouldBeNil)
target, err = wechat.dao.QueryUserByID(modify.UserID)
So(err, ShouldBeNil)
So(target.VisibleSaga, ShouldBeTrue)
So(target.UserName, ShouldNotEqual, "eyotang")
})
}
func TestPushMsg(t *testing.T) {
var err error
userName := []string{"wuwei"}
content := "测试发送企业微信"
Convey("TEST push message", t, func() {
err = wechat.PushMsg(ctx, userName, content)
So(err, ShouldBeNil)
})
}
func TestAnalysisContacts(t *testing.T) {
var err error
Convey("TEST analysis contacts", t, func() {
err = wechat.AnalysisContacts(ctx)
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,485 @@
{
"swagger": "2.0",
"info": {
"title": "go-common api",
"description": "api",
"version": "1.0",
"contact": {
"email": "lintanghui@bilibili.com"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
}
},
"paths": {
"/ep/admin/saga/v1/projects": {
"get": {
"operationId": "/ep/admin/saga/v1/projects",
"responses": {
"200": {
"description": "服务成功响应内容",
"schema": {
"type": "object",
"properties": {
"code": {
"description": "错误码描述",
"type": "integer"
},
"data": {
"$ref": "#/definitions/ProjectsResp",
"type": "object"
},
"message": {
"description": "错误码文本描述",
"type": "string"
},
"ttl": {
"description": "客户端限速时间",
"type": "integer",
"format": "int64"
}
}
}
}
}
}
},
"/ep/admin/saga/v1/projects/favorite": {
"get": {
"operationId": "/ep/admin/saga/v1/projects/favorite",
"responses": {
"200": {
"description": "服务成功响应内容",
"schema": {
"type": "object",
"properties": {
"code": {
"description": "错误码描述",
"type": "integer"
},
"data": {
"$ref": "#/definitions/FavoriteProjectsResp",
"type": "object"
},
"message": {
"description": "错误码文本描述",
"type": "string"
},
"ttl": {
"description": "客户端限速时间",
"type": "integer",
"format": "int64"
}
}
}
}
}
}
},
"/ep/admin/saga/v1/projects/favorite/edit": {
"post": {
"operationId": "/ep/admin/saga/v1/projects/favorite/edit",
"parameters": [
{
"in": "query",
"name": "proj_id",
"required": true,
"type": "integer",
"format": "int64"
},
{
"in": "query",
"name": "star",
"required": true,
"type": "boolean"
}
],
"responses": {
"200": {
"description": "服务成功响应内容",
"schema": {
"type": "object",
"properties": {
"code": {
"description": "错误码描述",
"type": "integer"
},
"data": {
"$ref": "#/definitions/EmptyResp",
"type": "object"
},
"message": {
"description": "错误码文本描述",
"type": "string"
},
"ttl": {
"description": "客户端限速时间",
"type": "integer",
"format": "int64"
}
}
}
}
}
}
},
"/ep/admin/saga/v1/tasks/project": {
"get": {
"operationId": "/ep/admin/saga/v1/tasks/project",
"parameters": [
{
"in": "query",
"name": "ProjID",
"type": "integer",
"format": "int64"
},
{
"in": "query",
"name": "Statuses",
"description": "3 - running, 4 - waiting, 默认查运行中和等待的任务 默认值 3,4",
"type": "array"
}
],
"responses": {
"200": {
"description": "服务成功响应内容",
"schema": {
"type": "object",
"properties": {
"code": {
"description": "错误码描述",
"type": "integer"
},
"data": {
"$ref": "#/definitions/TasksResp",
"type": "object"
},
"message": {
"description": "错误码文本描述",
"type": "string"
},
"ttl": {
"description": "客户端限速时间",
"type": "integer",
"format": "int64"
}
}
}
}
}
}
},
"/ep/admin/saga/v1/tasks/simple": {
"get": {
"operationId": "/ep/admin/saga/v1/tasks/simple",
"parameters": [
{
"in": "query",
"name": "Statuses",
"description": "3 - running, 4 - waiting, 默认查运行中和等待的任务 默认值 3,4",
"type": "array"
}
],
"responses": {
"200": {
"description": "服务成功响应内容",
"schema": {
"type": "object",
"properties": {
"code": {
"description": "错误码描述",
"type": "integer"
},
"data": {
"$ref": "#/definitions/SimpleTasksResp",
"type": "object"
},
"message": {
"description": "错误码文本描述",
"type": "string"
},
"ttl": {
"description": "客户端限速时间",
"type": "integer",
"format": "int64"
}
}
}
}
}
}
},
"/ep/admin/saga/v1/user": {
"get": {
"operationId": "/ep/admin/saga/v1/user",
"responses": {
"200": {
"description": "服务成功响应内容",
"schema": {
"type": "object",
"properties": {
"code": {
"description": "错误码描述",
"type": "integer"
},
"data": {
"$ref": "#/definitions/User",
"type": "object"
},
"message": {
"description": "错误码文本描述",
"type": "string"
},
"ttl": {
"description": "客户端限速时间",
"type": "integer",
"format": "int64"
}
}
}
}
}
}
}
},
"definitions": {
"EmptyResp": {
"title": "EmptyResp",
"description": "EmptyResp resp for response without data",
"type": "object"
},
"FavoriteProjectsResp": {
"title": "FavoriteProjectsResp",
"description": "FavoriteProjectsResp resp for favorite projects",
"type": "object",
"properties": {
"projects": {
"type": "array",
"items": {
"$ref": "#/definitions/Project",
"type": "object"
}
}
}
},
"MyProject": {
"title": "MyProject",
"description": "MyProject mask as star",
"required": [
"is_star"
],
"type": "object",
"properties": {
"is_star": {
"type": "boolean"
}
}
},
"Project": {
"title": "Project",
"description": "Project def",
"required": [
"name",
"description",
"web_url",
"avatar_url",
"git_ssh_url",
"git_http_url",
"namespace",
"visibility_level",
"path_with_namespace",
"default_branch",
"homepage",
"url",
"ssh_url",
"http_url"
],
"type": "object",
"properties": {
"avatar_url": {
"type": "string"
},
"default_branch": {
"type": "string"
},
"description": {
"type": "string"
},
"git_http_url": {
"type": "string"
},
"git_ssh_url": {
"type": "string"
},
"homepage": {
"type": "string"
},
"http_url": {
"type": "string"
},
"name": {
"type": "string"
},
"namespace": {
"type": "string"
},
"path_with_namespace": {
"type": "string"
},
"ssh_url": {
"type": "string"
},
"url": {
"type": "string"
},
"visibility_level": {
"type": "integer",
"format": "int64"
},
"web_url": {
"type": "string"
}
}
},
"ProjectsResp": {
"title": "ProjectsResp",
"description": "ProjectsResp resp for query projects with start mark",
"required": [
"projects"
],
"type": "object",
"properties": {
"projects": {
"type": "array",
"items": {
"$ref": "#/definitions/MyProject",
"type": "object"
}
}
}
},
"SimpleProject": {
"title": "SimpleProject",
"description": "SimpleProject with project id, project name and tasks",
"required": [
"proj_id",
"proj_name",
"tasks"
],
"type": "object",
"properties": {
"proj_id": {
"type": "integer",
"format": "int64"
},
"proj_name": {
"type": "string"
},
"tasks": {
"type": "array",
"items": {
"$ref": "#/definitions/SimpleTask",
"type": "object"
}
}
}
},
"SimpleTasksResp": {
"title": "SimpleTasksResp",
"description": "SimpleTasksResp resp for simple tasks",
"required": [
"projects"
],
"type": "object",
"properties": {
"projects": {
"type": "array",
"items": {
"$ref": "#/definitions/SimpleProject",
"type": "object"
}
}
}
},
"Task": {
"title": "Task",
"description": "Task def",
"type": "object",
"properties": {
"author": {
"type": "string"
},
"event_type": {
"type": "string"
},
"id": {
"type": "integer",
"format": "int64"
},
"mr_id": {
"type": "integer",
"format": "int64"
},
"proj_id": {
"type": "integer",
"format": "int64"
},
"source_branch": {
"type": "string"
},
"status": {
"type": "integer",
"format": "int64"
},
"target_branch": {
"type": "string"
},
"task_details": {
"type": "string"
},
"title": {
"type": "string"
},
"url": {
"type": "string"
}
}
},
"TasksResp": {
"title": "TasksResp",
"description": "TasksResp resp for tasks",
"type": "object",
"properties": {
"tasks": {
"type": "array",
"items": {
"$ref": "#/definitions/Task",
"type": "object"
}
}
}
},
"User": {
"title": "User",
"description": "User def",
"required": [
"name",
"username",
"avatar_url"
],
"type": "object",
"properties": {
"avatar_url": {
"type": "string"
},
"name": {
"type": "string"
},
"username": {
"type": "string"
}
}
}
}
}