feat: service
continuous-integration/drone/push Build was killed Details

master
icechen 2022-01-11 04:00:39 +08:00
parent 35119f2003
commit baa0eb8db2
22 changed files with 450 additions and 34 deletions

View File

@ -44,8 +44,14 @@ spec:
- containerPort: {{ .Values.port }} - containerPort: {{ .Values.port }}
protocol: TCP protocol: TCP
env: env:
- name: endpoints - name: ENDPOINTS
value: 'etcd:2379' value: 'etcd:2379'
- name: APP_NAME
value: {{ .Values.appName }}
- name: NAMESPACE
value: {{ .Values.nameSpace }}
- name: APP_TYPE
value: api
resources: resources:
limits: limits:
cpu: 200m cpu: 200m

View File

@ -1,4 +1,5 @@
nameSpace: zeus nameSpace: zeus
appName: lark
aliasName: 飞书 aliasName: 飞书
image: reg.icechen.cn/zeus/api-lark image: reg.icechen.cn/zeus/api-lark
imageTag: latest imageTag: latest

View File

@ -0,0 +1,20 @@
FROM golang:1.17 as builder
ENV GO111MODULE on
ENV GOPROXY https://goproxy.io,direct
WORKDIR /go/cache
ADD go.mod .
ADD go.sum .
RUN go mod download -x
WORKDIR /go/src
ADD . .
RUN go mod tidy
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o service_lark ./app/zeus/service/lark
FROM reg.icechen.cn/alpine as lark
WORKDIR /go/src
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=builder /go/src/service_lark ./
ENV TZ=Asia/Shanghai
RUN chmod +x ./service_lark
EXPOSE 8080
CMD ["./service_lark"]

View File

@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

View File

@ -0,0 +1,24 @@
apiVersion: v2
name: lark
description: 飞书
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.0.1
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "0.0.1"

View File

@ -0,0 +1 @@
notes

View File

@ -0,0 +1,20 @@
apiVersion: app.k8s.io/v1beta1
kind: Application
metadata:
name: {{ .Release.Name }}
namespace: {{ .Values.nameSpace }}
labels:
app: {{ .Release.Name }}
app.kubernetes.io/version: v1
app.kubernetes.io/name: {{ .Release.Name }}
version: v1
annotations:
servicemesh.kubesphere.io/enabled: 'true'
spec:
selector:
matchLabels:
app: {{ .Release.Name }}
app.kubernetes.io/version: v1
app.kubernetes.io/name: {{ .Release.Name }}
version: v1
addOwnerRef: true

View File

@ -0,0 +1,82 @@
kind: Deployment
apiVersion: apps/v1
metadata:
name: {{ .Release.Name }}
namespace: {{ .Values.nameSpace }}
labels:
app: {{ .Release.Name }}
app.kubernetes.io/version: v1
app.kubernetes.io/name: {{ .Release.Name }}
version: v1
app.kubernetes.io/instance: {{ .Release.Name }}
annotations:
kubesphere.io/alias-name: {{ .Values.aliasName }}
kubesphere.io/creator: drone
meta.helm.sh/release-name: {{ .Release.Name }}
meta.helm.sh/release-namespace: {{ .Values.nameSpace }}
spec:
replicas: 1
selector:
matchLabels:
app: {{ .Release.Name }}
app.kubernetes.io/version: v1
app.kubernetes.io/name: {{ .Release.Name }}
version: v1
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app: {{ .Release.Name }}
app.kubernetes.io/version: v1
app.kubernetes.io/name: {{ .Release.Name }}
version: v1
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
volumes:
- name: host-time
hostPath:
path: /etc/localtime
type: ''
containers:
- name: {{ .Release.Name }}
image: {{ .Values.image }}:{{ .Values.imageTag }}
ports:
- containerPort: {{ .Values.port }}
protocol: TCP
env:
- name: ENDPOINTS
value: 'etcd:2379'
- name: APP_NAME
value: {{ .Values.appName }}
- name: NAMESPACE
value: {{ .Values.nameSpace }}
- name: APP_TYPE
value: service
resources:
limits:
cpu: 200m
memory: 1000Mi
requests:
cpu: 10m
memory: 200Mi
volumeMounts:
- name: host-time
readOnly: true
mountPath: /etc/localtime
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
imagePullPolicy: IfNotPresent
restartPolicy: Always
terminationGracePeriodSeconds: 30
dnsPolicy: ClusterFirst
securityContext: {}
imagePullSecrets:
- name: registry-secret
schedulerName: default-scheduler
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 25%
maxSurge: 25%
revisionHistoryLimit: 10
progressDeadlineSeconds: 600

View File

@ -0,0 +1,26 @@
kind: Service
apiVersion: v1
metadata:
name: {{ .Release.Name }}
namespace: {{ .Values.nameSpace }}
annotations:
kubesphere.io/creator: drone
labels:
app: {{ .Release.Name }}
app.kubernetes.io/version: v1
app.kubernetes.io/name: {{ .Release.Name }}
version: v1
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
ports:
- name: http-{{ .Release.Name }}
protocol: TCP
port: 80
targetPort: {{ .Values.port }}
selector:
app: {{ .Release.Name }}
type: ClusterIP
sessionAffinity: None
ipFamilies:
- IPv4
ipFamilyPolicy: SingleStack

View File

@ -0,0 +1,6 @@
nameSpace: zeus
appName: lark
aliasName: 飞书
image: reg.icechen.cn/zeus/service-lark
imageTag: latest
port: 3000

View File

@ -0,0 +1,17 @@
package config
import "git.icechen.cn/monorepo/backend/pkg/config"
type Cfg struct {
LarkAppID string `json:"lark_app_id"`
LarkAppSecret string `json:"lark_app_secret"`
}
var Config Cfg
func init() {
err := config.GetConfig(&Config)
if err != nil {
panic(err)
}
}

View File

@ -0,0 +1,31 @@
package lark
import (
"context"
cfg "git.icechen.cn/monorepo/backend/app/zeus/service/lark/internal/config"
"github.com/larksuite/oapi-sdk-go/core"
"github.com/larksuite/oapi-sdk-go/core/config"
authen "github.com/larksuite/oapi-sdk-go/service/authen/v1"
)
type Lark struct {
conf *config.Config
}
func NewLark() Lark {
appSettings := core.NewInternalAppSettings(
core.SetAppCredentials(cfg.Config.LarkAppID, cfg.Config.LarkAppSecret))
conf := core.NewConfig(core.DomainFeiShu, appSettings, core.SetLoggerLevel(core.LoggerLevelError))
return Lark{
conf: conf,
}
}
func (l Lark) Auth(code string) (*authen.UserAccessTokenInfo, error) {
s := authen.NewService(l.conf)
return s.Authens.AccessToken(core.WrapContext(context.Background()), &authen.AuthenAccessTokenReqBody{
GrantType: "",
Code: code,
}).Do()
}

View File

@ -0,0 +1,50 @@
package lark
import (
"context"
"fmt"
"google.golang.org/grpc/keepalive"
"log"
"net"
"time"
"git.icechen.cn/monorepo/backend/pkg/proto/zeus/lark"
"github.com/jinzhu/copier"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)
func RpcServer() {
lis, err := net.Listen("tcp", ":3000")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer(grpc.KeepaliveParams(keepalive.ServerParameters{
MaxConnectionIdle: 5 * time.Minute,
}))
lark.RegisterUserServer(s, &Server{})
reflection.Register(s)
fmt.Println("lark server run in :3000")
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
type Server struct{}
func (s Server) Login(ctx context.Context, in *lark.LoginRequest) (*lark.LoginResponse, error) {
userInfo, err := NewLark().Auth(in.Code)
if err != nil {
return nil, err
}
resp := new(lark.LoginResponse)
err = copier.Copy(resp, userInfo)
if err != nil {
return nil, err
}
return resp, nil
}

View File

@ -0,0 +1,9 @@
package main
import (
"git.icechen.cn/monorepo/backend/app/zeus/service/lark/internal/lark"
)
func main() {
lark.RpcServer()
}

View File

@ -0,0 +1,14 @@
package model
import (
"gorm.io/gorm"
)
var db *gorm.DB
func init() {
err := db.AutoMigrate(&UserInfo{})
if err != nil {
panic(err)
}
}

View File

@ -0,0 +1,28 @@
package model
import "gorm.io/gorm"
type UserInfo struct {
gorm.Model
AccessToken string `json:"access_token,omitempty" gorm:"access_token"`
TokenType string `json:"token_type,omitempty" gorm:"token_type"`
ExpiresIn int `json:"expires_in,omitempty" gorm:"expires_in"`
Name string `json:"name,omitempty" gorm:"name"`
EnName string `json:"en_name,omitempty" gorm:"en_name"`
AvatarUrl string `json:"avatar_url,omitempty" gorm:"avatar_url"`
AvatarThumb string `json:"avatar_thumb,omitempty" gorm:"avatar_thumb"`
AvatarMiddle string `json:"avatar_middle,omitempty" gorm:"avatar_middle"`
AvatarBig string `json:"avatar_big,omitempty" gorm:"avatar_big"`
OpenId string `json:"open_id,omitempty" gorm:"open_id"`
UnionId string `json:"union_id,omitempty" gorm:"union_id"`
Email string `json:"email,omitempty" gorm:"email"`
UserId string `json:"user_id,omitempty" gorm:"user_id"`
Mobile string `json:"mobile,omitempty" gorm:"mobile"`
TenantKey string `json:"tenant_key,omitempty" gorm:"tenant_key"`
RefreshExpiresIn int `json:"refresh_expires_in,omitempty" gorm:"refresh_expires_in"`
RefreshToken string `json:"refresh_token,omitempty" gorm:"refresh_token"`
}
func (UserInfo) TableName() string {
return "user_info"
}

View File

@ -3,48 +3,70 @@ package config
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"git.icechen.cn/monorepo/backend/pkg/env"
"git.icechen.cn/monorepo/backend/pkg/etcd" "git.icechen.cn/monorepo/backend/pkg/etcd"
"github.com/pkg/errors" "github.com/pkg/errors"
"reflect"
) )
const ( const (
prefix = "/config" prefix = "/config"
env = "/env" envKey = "/env"
) )
type Env string
const ( const (
EnvTest = "test" EnvTest Env = "test"
EnvOnl = "onl" EnvOnl Env = "onl"
) )
// GetConfig 根据etcd获取存入配置 // GetConfig 根据etcd获取存入配置
func GetConfig(name string, config interface{}) error { func GetConfig(config interface{}) error {
key := fmt.Sprintf("%v/config/%v", prefix, name) return Bind("config", config)
}
// GetMysql 获取数据库连接字符串
func GetMysql() (string, error) {
return GetString("mysql")
}
// Bind 读取配置项到结构体
func Bind(item string, out interface{}) error {
if reflect.TypeOf(out).Kind() != reflect.Ptr {
return errors.New("bind对象不是指针类型")
}
key := fmt.Sprintf("%s/%s/%s/%s", prefix, env.Namespace, env.GetAppNameWithType(), item)
value, err := etcd.GetValue(key) value, err := etcd.GetValue(key)
if err != nil { if err != nil {
return err return err
} }
err = json.Unmarshal([]byte(value), config) err = json.Unmarshal([]byte(value), out)
if err != nil { if err != nil {
return errors.WithMessage(err, "etcd配置解析到json失败") return errors.WithMessage(err, "etcd配置解析到json失败")
} }
return nil return nil
} }
func GetMysql(name string) (string, error) { // GetString 读取配置项字符串
key := fmt.Sprintf("%v/mysql/%v", prefix, name) func GetString(item string) (string, error) {
key := fmt.Sprintf("%s/%s/%s/%s", prefix, env.Namespace, env.GetAppNameWithType(), item)
return etcd.GetValue(key) return etcd.GetValue(key)
} }
func GetEnv() (string, error) { // GetEnv 获取环境
return etcd.GetValue(env) func GetEnv() (Env, error) {
envString, err := etcd.GetValue(envKey)
return Env(envString), err
} }
// IsTest 是否是测试环境
func IsTest() bool { func IsTest() bool {
envString, _ := GetEnv() envString, _ := GetEnv()
return envString == EnvTest return envString == EnvTest
} }
// IsOnl 是否是正式环境
func IsOnl() bool { func IsOnl() bool {
envString, _ := GetEnv() envString, _ := GetEnv()
return envString == EnvOnl return envString == EnvOnl

View File

@ -2,6 +2,8 @@ package config
import ( import (
"fmt" "fmt"
"io/ioutil"
"net/http"
"testing" "testing"
) )
@ -10,24 +12,11 @@ type config struct {
Es string `json:"es,omitempty"` Es string `json:"es,omitempty"`
} }
func TestGetMysql(t *testing.T) {
//获取 /config/mysql/api-zeus 的str
mysql, err := GetMysql("api-zeus")
if err != nil {
fmt.Println(err)
}
fmt.Println(mysql)
}
func TestConfig(t *testing.T) {
c := new(config)
err := GetConfig("api-zeus", c)
if err != nil {
fmt.Println(err)
}
}
func TestEnv(t *testing.T) { func TestEnv(t *testing.T) {
resp, err := http.Get("http://api-lark-test/user")
b, _ := ioutil.ReadAll(resp.Body)
t.Log(string(b))
env, err := GetEnv() env, err := GetEnv()
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)

27
pkg/env/env.go vendored
View File

@ -1,6 +1,26 @@
package env package env
import "os" import (
"fmt"
"os"
)
func init() {
AppName = GetEnvDefault("APP_NAME", "default")
Namespace = GetEnvDefault("NAMESPACE", "default")
AppType = SAppType(GetEnvDefault("APP_TYPE", string(AppTypeAPI)))
}
var AppName string
var Namespace string
var AppType SAppType
type SAppType string
const (
AppTypeAPI = SAppType("api") // api应用类型
AppTypeService = SAppType("service") // service应用类型
)
func GetEnvDefault(key, defVal string) string { func GetEnvDefault(key, defVal string) string {
val, ex := os.LookupEnv(key) val, ex := os.LookupEnv(key)
@ -10,3 +30,8 @@ func GetEnvDefault(key, defVal string) string {
} }
return val return val
} }
// GetAppNameWithType 获取带应用类型的应用名
func GetAppNameWithType() string {
return fmt.Sprintf("%s-%s", AppType, AppName)
}

View File

@ -11,8 +11,8 @@ import (
) )
const ( const (
EndPoints = "endpoints" EndPoints = "ENDPOINTS"
EndAddress = "etcd:2379" EndAddress = "etcd1:2379"
) )
var ( var (
@ -40,11 +40,11 @@ func GetValue(key string) (string, error) {
} }
kv := clientV3.NewKV(client) kv := clientV3.NewKV(client)
defer client.Close() defer client.Close()
ctx, cancel := context.WithTimeout(context.TODO(), time.Second*3) ctx, cancel := context.WithTimeout(context.TODO(), time.Minute*3)
defer cancel() defer cancel()
if getResp, err := kv.Get(ctx, key, clientV3.WithPrefix()); err == nil { if getResp, err := kv.Get(ctx, key, clientV3.WithPrefix()); err == nil {
for _, v := range getResp.Kvs { if len(getResp.Kvs) > 0 {
return string(v.Value), nil return string(getResp.Kvs[0].Value), nil
} }
} else { } else {
return "", errors.New("etcd 连接超时") return "", errors.New("etcd 连接超时")

22
pkg/orm/db.go 100644
View File

@ -0,0 +1,22 @@
package orm
import (
"fmt"
"git.icechen.cn/monorepo/backend/pkg/config"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
var DB *gorm.DB
func init() {
dsn, err := config.GetMysql()
if err != nil {
fmt.Println("init db error: ", err)
return
}
DB, err = gorm.Open(mysql.Open(dsn))
if err != nil {
panic(err)
}
}