init项目

This commit is contained in:
qiyunfanbo126.com 2024-04-29 15:13:21 +08:00
commit 8c11d75d8c
53 changed files with 5679 additions and 0 deletions

62
.env.example Normal file
View File

@ -0,0 +1,62 @@
# toml配置文件
# Wikihttps://github.com/toml-lang/toml
ServiceName = "snow"
Debug = true
Env = "local" # local-本地 develop-开发 beta-预发布 production-线上
PrometheusCollectEnable = true
SkyWalkingOapServer = "127.0.0.1:11800"
[Log]
Handler = "file"
Dir = "./logs"
Level = "info"
[Db]
Driver = "mysql"
[Db.Option]
MaxConns = 128
MaxIdle = 32
IdleTimeout = 180 # second
Charset = "utf8mb4"
ConnectTimeout = 3 # second
[Db.Master]
Host = "127.0.0.1"
Port = 3306
User = "root"
Password = "123456"
DBName = "test"
[[Db.Slaves]] # 支持多个从库
Host = "127.0.0.1"
Port = 3306
User = "root"
Password = "123456"
DBName = "test"
[Api]
Host = "0.0.0.0"
Port = 8080
[Cache]
Driver = "redis"
[Redis.Master]
Host = "127.0.0.1"
Port = 6379
#Password = ""
#DB = 0
#[Redis.Option]
#MaxIdle = 64
#MaxConns = 256
#IdleTimeout = 180 # second
#ConnectTimeout = 1
#ReadTimeout = 1
#WriteTimeout = 1
[AliMns]
Url = ""
AccessKeyId = ""
AccessKeySecret = ""

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/.idea
/vendor
/.env
!/.env.example

74
README.md Normal file
View File

@ -0,0 +1,74 @@
## Snow
Snow是一套简单易用的Go语言业务框架整体逻辑设计简洁支持HTTP服务、队列调度和任务调度等常用业务场景模式。
## Quick start
### Build
sh build/shell/build.sh
### Run
```shell
1. build/bin/snow -a api #启动Api服务
2. build/bin/snow -a cron #启动Cron定时任务服务
3. build/bin/snow -a job #启动队列调度服务
4. build/bin/snow -a command -m test #执行名称为test的脚本任务
```
## Documents
- [项目地址](https://github.com/qit-team/snow)
- [中文文档](https://github.com/qit-team/snow/wiki)
- [changelog](https://github.com/qit-team/snow/blob/master/CHANGLOG.md)
- [xorm](http://gobook.io/read/github.com/go-xorm/manual-zh-CN/)
##
<!--
// validator的示例
// HandleTestValidator godoc
// @Summary HandleTestValidator的示例
// @Description HandleTestValidator的示例
// @Tags snow
// @Accept json
// @Produce json
// @Param testValidator body entities.TestValidatorRequest true "example of validator"
// @Success 200 {array} entities.TestValidatorRequest
// @Failure 400 {object} controllers.HTTPError
// @Failure 404 {object} controllers.HTTPError
// @Failure 500 {object} controllers.HTTPError
// @Router /test_validator [post]
func HandleTestValidator(c *gin.Context) {
request := new(entities.TestValidatorRequest)
err := GenRequest(c, request)
if err != nil {
Error(c, errorcode.ParamError)
return
}
Success(c, request)
return
}
// Address houses a users address information
type Address struct {
Street string `json:"street" validate:"required" example:"huandaodonglu"`
City string `json:"city" validate:"required" example:"xiamen"`
Planet string `json:"planet" validate:"required" example:"snow"`
Phone string `json:"phone" validate:"required" example:"snow"`
}
// 请求数据结构
type TestValidatorRequest struct {
//tips因为组件required不管是没传值或者传 0 or "" 都通过不了但是如果用指针类型那么0就是0而nil无法通过校验
Id *int64 `json:"id" validate:"required" example:"1"`
Age int `json:"age" validate:"required,gte=0,lte=130" example:"20"`
Name *string `json:"name" validate:"required" example:"snow"`
Email string `json:"email" validate:"required,email" example:"snow@github.com"`
Url string `json:"url" validate:"required" example:"github.com/qit-team/snow"`
Mobile string `json:"mobile" validate:"required" example:"snow"`
RangeNum int `json:"range_num" validate:"max=10,min=1" example:"3"`
TestNum *int `json:"test_num" validate:"required,oneof=5 7 9" example:"7"`
Content *string `json:"content" example:"snow"`
Addresses []*Address `json:"addresses" validate:"required,dive,required" `
}
-->

View File

@ -0,0 +1,34 @@
package bannerlistcache
import (
"sync"
"qteam/app/caches"
"github.com/qit-team/snow-core/cache"
)
const (
prefix = caches.BannerList //缓存key的前缀
)
var (
instance *bannerListCache
once sync.Once
)
type bannerListCache struct {
cache.BaseCache
}
//单例模式
func GetInstance() *bannerListCache {
once.Do(func() {
instance = new(bannerListCache)
instance.Prefix = prefix
//instance.DiName = redis.SingletonMain //设置缓存依赖的实例别名
//instance.DriverType = cache.DriverTypeRedis //设置缓存驱动的类型,默认redis
//instance.SeTTL(86400) 设置默认缓存时间 默认86400
})
return instance
}

View File

@ -0,0 +1,59 @@
package bannerlistcache
import (
"context"
"fmt"
"testing"
"qteam/config"
"github.com/qit-team/snow-core/cache"
_ "github.com/qit-team/snow-core/cache/rediscache"
"github.com/qit-team/snow-core/redis"
)
func init() {
//加载配置文件
conf, err := config.Load("../../../.env")
if err != nil {
fmt.Println(err)
}
//注册redis类
err = redis.Pr.Register(cache.DefaultDiName, conf.Redis)
if err != nil {
fmt.Println(err)
}
}
func Test_GetMulti(t *testing.T) {
ctx := context.TODO()
cache := GetInstance()
res, _ := cache.Set(ctx, "1000", "a")
if res != true {
t.Errorf("set key:%s is error", "1000")
}
keys := []string{"1000", "-2000", "9999"}
cacheList, err := cache.GetMulti(ctx, keys...)
if err != nil {
t.Errorf("getMulti error:%s", err.Error())
}
fmt.Println(cacheList)
i := 0
for k, v := range cacheList {
i++
if k == "1000" {
if v != "a" {
t.Errorf("value of key:%s is error %v", k, v)
}
} else {
if v != "" {
t.Errorf("value of key:%s is error %v", k, v)
}
}
}
if i != len(keys) {
t.Errorf("count of cache key is error: %d", i)
}
}

8
app/caches/cache_key.go Normal file
View File

@ -0,0 +1,8 @@
package caches
//缓存前缀key不同的业务使用不同的前缀避免了业务之间的重用冲突
const (
Cookie = "ck:"
Copy = "cp:"
BannerList = "bl:"
)

9
app/console/command.go Normal file
View File

@ -0,0 +1,9 @@
package console
import (
"github.com/qit-team/snow-core/command"
)
func RegisterCommand(c *command.Command) {
c.AddFunc("test", test)
}

15
app/console/kernel.go Normal file
View File

@ -0,0 +1,15 @@
package console
import (
"github.com/robfig/cron"
)
/**
* 配置执行计划
* @wiki https://godoc.org/github.com/robfig/cron
*/
func RegisterSchedule(c *cron.Cron) {
//c.AddFunc("0 30 * * * *", test)
//c.AddFunc("@hourly", test)
c.AddFunc("@every 10s", test)
}

7
app/console/test.go Normal file
View File

@ -0,0 +1,7 @@
package console
import "fmt"
func test() {
fmt.Println("run test")
}

View File

@ -0,0 +1,5 @@
package common
const (
TOKEN_PRE = "player_token_"
)

View File

@ -0,0 +1,5 @@
package common
const (
Event_USER_LOG_IN = "user_login"
)

View File

@ -0,0 +1,41 @@
package errorcode
const (
//成功
Success = 200
//参数错误
ParamError = 400
//未经授权
NotAuth = 401
//请求被禁止
Forbidden = 403
//找不到页面
NotFound = 404
//系统错误
SystemError = 500
)
var MsgEN = map[int]string{
Success: "success",
ParamError: "param error",
NotAuth: "not authorized",
Forbidden: "forbidden",
NotFound: "not found",
SystemError: "system error",
}
var MsgMap map[string]map[int]string = map[string]map[int]string{"en": MsgEN}
func GetMsg(code int, local string) string {
if local == "" {
local = "en"
}
if msg, ok := MsgMap[local][code]; ok {
return msg
}
return ""
}

View File

@ -0,0 +1,7 @@
package logtype
const (
Message = "message"
GoPanic = "go.panic"
HTTP = "http"
)

View File

@ -0,0 +1,163 @@
package controllers
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"github.com/qit-team/snow-core/redis"
"gopkg.in/go-playground/validator.v9"
zh_translations "gopkg.in/go-playground/validator.v9/translations/zh"
"io/ioutil"
"net/http"
"qteam/app/utils"
"qteam/config"
"qteam/app/constants/errorcode"
"github.com/gin-gonic/gin"
)
/**
* 成功时返回
*/
func Success(c *gin.Context, data interface{}, message string) {
if message == "" {
message = errorcode.GetMsg(errorcode.Success, c.GetHeader("local"))
}
if config.GetConf().Env == "production" {
c.String(http.StatusOK, EncriptJson(gin.H{
"code": errorcode.Success,
"message": message,
"data": data,
}))
} else {
c.JSON(http.StatusOK, gin.H{
"code": errorcode.Success,
"message": message,
"data": data,
})
}
c.Abort()
}
func EncriptJson(h gin.H) string {
var data, err = json.Marshal(h)
if err != nil {
utils.Log(nil, "encriptJso", err)
}
rs, err := utils.Des3Encrypt(data, config.GetConf().AppKey)
res := base64.StdEncoding.EncodeToString(rs)
return res
}
/**
* 失败时返回
*/
func Error(c *gin.Context, code int, msg ...string) {
message := ""
if len(msg) > 0 {
message = msg[0]
} else {
message = errorcode.GetMsg(code, "")
}
if config.GetConf().Env == "production" {
c.String(http.StatusOK, EncriptJson(gin.H{
"code": code,
"message": message,
"data": make(map[string]string),
}))
} else {
c.JSON(http.StatusOK, gin.H{
"code": code,
"message": message,
"data": make(map[string]string),
})
}
c.Abort()
}
func Error404(c *gin.Context) {
Error(c, errorcode.NotFound, "路由不存在")
}
func Error500(c *gin.Context) {
Error(c, errorcode.SystemError)
}
type HTTPError struct {
Code int `json:"code" example:"400"`
Message string `json:"message" example:"status bad request"`
}
/**
* 将请求的body转换为request数据结构
* @param c
* @param request 传入request数据结构的指针 new(TestRequest)
*/
func GenRequest(c *gin.Context, request interface{}) (err error) {
body, err := ReadBody(c)
if err != nil {
return
}
err = json.Unmarshal(body, request)
if err == nil {
validate := validator.New()
zh_ch := zh.New()
uni := ut.New(zh_ch)
trans, _ := uni.GetTranslator("zh")
//验证器注册翻译器
zh_translations.RegisterDefaultTranslations(validate, trans)
errValidate := validate.Struct(request)
if errValidate != nil {
utils.Log(c, "param_validator_exception:"+c.Request.URL.Path, errValidate)
return errValidate
}
}
return err
}
// 重复读取body
func ReadBody(c *gin.Context) (body []byte, err error) {
body, err = ioutil.ReadAll(c.Request.Body)
if err != nil {
return
}
c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(body))
return
}
func HandRes(c *gin.Context, data interface{}, err error) {
if err == nil {
Success(c, data, "")
} else {
Error(c, errorcode.SystemError, err.Error())
}
}
func HandCodeRes(c *gin.Context, data interface{}, code int) {
if code == errorcode.Success {
Success(c, data, errorcode.GetMsg(code, c.GetHeader("local")))
} else {
Error(c, code, errorcode.GetMsg(code, c.GetHeader("local")))
}
}
func GetPlayerId(c *gin.Context) string {
playerId, _ := c.Get("playerId")
if playerId == nil {
return ""
}
return playerId.(string)
}
func Frequence(key string) bool {
if rs := redis.GetRedis().Exists(context.Background(), utils.GetRealKey(key)); rs.String() != "" {
return false
} else {
redis.GetRedis().SetEX(context.Background(), utils.GetRealKey(key), 1, 5)
return true
}
}

View File

@ -0,0 +1,34 @@
package bannerformatter
import (
"qteam/app/models/bannermodel"
)
type BannerFormatter struct {
Id int `json:"id"`
Title string `json:"title"`
Img string `json:"image"`
Url string `json:"url"`
}
func FormatList(bannerList []*bannermodel.Banner) (res []*BannerFormatter) {
res = make([]*BannerFormatter, len(bannerList))
for k, banner := range bannerList {
one := FormatOne(banner)
res[k] = one
}
return res
}
//单条消息的格式化,
func FormatOne(banner *bannermodel.Banner) (res *BannerFormatter) {
res = &BannerFormatter{
Id: int(banner.Id),
Title: banner.Title,
Img: banner.ImageUrl,
Url: banner.Url,
}
return
}

View File

@ -0,0 +1,45 @@
package bannerformatter
import (
"testing"
"qteam/app/models/bannermodel"
)
func TesFormatOne(t *testing.T) {
a := &bannermodel.Banner{
Id: 1,
Title: "test",
ImageUrl: "http://x/1.jpg",
Url: "http://x",
Status: "1",
}
b := FormatOne(a)
if b.Title != a.Title || b.Img != a.ImageUrl || b.Url != a.Url {
t.Error("FormatOne not same")
}
}
func TesFormatList(t *testing.T) {
a := make([]*bannermodel.Banner, 2)
a[0] = &bannermodel.Banner{
Id: 1,
Title: "test",
ImageUrl: "http://x1/1.jpg",
Url: "http://x1",
Status: "1",
}
a[1] = &bannermodel.Banner{
Id: 2,
Title: "test2",
ImageUrl: "http://x/2.jpg",
Url: "http://x2",
Status: "2",
}
b := FormatList(a)
for k, v := range b {
if v.Title != a[k].Title || v.Img != a[k].ImageUrl || v.Url != a[k].Url {
t.Error("FormatList not same")
}
}
}

77
app/http/metric/metric.go Normal file
View File

@ -0,0 +1,77 @@
package metric
import (
"net/http"
"qteam/app/utils/metric"
"github.com/prometheus/client_golang/prometheus"
)
const (
HOST = "host"
PATH = "path" // 路径
METHOD = "method" // 方法
CODE = "code" // 错误码
// metric
ALL_REQ_TOTAL_COUNT = "all_req_total_count" // 所有URL总请求数
ALL_REQ_COST_TIME = "all_req_cost_time" // 所有URL请求耗时
REQ_TOTAL_COUNT = "req_total_count" // 每个URL总请求数
REQ_COST_TIME = "req_cost_time" // 每个URL请求耗时
)
func init() {
metric.RegisterCollector(reqTotalCounter, reqCostTimeObserver, allReqTotalCounter, allReqCostTimeObserver)
}
var (
reqTotalCounter = prometheus.NewCounterVec(prometheus.CounterOpts{
Name: REQ_TOTAL_COUNT,
}, []string{PATH, METHOD})
reqCostTimeObserver = prometheus.NewHistogramVec(prometheus.HistogramOpts{
Name: REQ_COST_TIME,
Buckets: []float64{
100,
200,
500,
1000,
3000,
5000,
},
}, []string{PATH, METHOD})
allReqTotalCounter = prometheus.NewCounterVec(prometheus.CounterOpts{
Name: ALL_REQ_TOTAL_COUNT,
}, []string{HOST})
allReqCostTimeObserver = prometheus.NewHistogramVec(prometheus.HistogramOpts{
Name: ALL_REQ_COST_TIME,
Buckets: []float64{
100,
200,
500,
1000,
3000,
5000,
},
}, []string{HOST})
)
func AddReqCount(req *http.Request) {
reqTotalCounter.WithLabelValues(req.URL.Path, req.Method).Inc()
}
func CollectReqCostTime(req *http.Request, ms int64) {
reqCostTimeObserver.WithLabelValues(req.URL.Path, req.Method).Observe(float64(ms))
}
func AddAllReqCount(req *http.Request) {
allReqTotalCounter.WithLabelValues(req.Host).Inc()
}
func CollectAllReqCostTime(req *http.Request, ms int64) {
allReqCostTimeObserver.WithLabelValues(req.Host).Observe(float64(ms))
}

View File

@ -0,0 +1,40 @@
package middlewares
import (
"context"
"errors"
"github.com/gin-gonic/gin"
"github.com/qit-team/snow-core/redis"
"qteam/app/constants/common"
"qteam/app/constants/errorcode"
"qteam/app/http/controllers"
"qteam/app/utils"
)
func Auth() gin.HandlerFunc {
return func(c *gin.Context) {
var token = c.GetHeader("token")
//将token放入redis
var playerId, err = redis.GetRedis().Get(context.Background(), utils.GetRealKey(common.TOKEN_PRE+token)).Result()
if rs, errRedis := redis.GetRedis().SIsMember(context.Background(), "disabled_uids", playerId).Result(); errRedis == nil && rs {
err = errors.New(errorcode.GetMsg(errorcode.NotFound, ""))
redis.GetRedis().SRem(context.Background(), "disabled_uids", playerId)
}
if err == nil {
c.Set("playerId", playerId)
c.Next()
return
} else {
controllers.HandCodeRes(c, nil, errorcode.Forbidden)
c.Abort()
}
}
}
func AdminAuth() gin.HandlerFunc {
return func(c *gin.Context) {
}
}

View File

@ -0,0 +1,22 @@
package middlewares
import (
"time"
"qteam/app/http/metric"
"github.com/gin-gonic/gin"
)
func CollectMetric() gin.HandlerFunc {
return func(ctx *gin.Context) {
start := time.Now()
ctx.Next()
dur := time.Now().Sub(start).Milliseconds()
metric.AddAllReqCount(ctx.Request)
metric.CollectAllReqCostTime(ctx.Request, dur)
metric.AddReqCount(ctx.Request)
metric.CollectReqCostTime(ctx.Request, dur)
}
}

View File

@ -0,0 +1,50 @@
package middlewares
import (
"encoding/json"
syslog "log"
"net/http/httputil"
"runtime/debug"
"qteam/app/constants/logtype"
"qteam/config"
"github.com/gin-gonic/gin"
"github.com/qit-team/snow-core/log/logger"
)
func ServerRecovery() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
httpRequest, _ := httputil.DumpRequest(c.Request, false)
msg := map[string]interface{}{
"error": err,
"request": string(httpRequest),
"stack": string(debug.Stack()),
}
msgJson, _ := json.Marshal(msg)
logger.GetLogger().Error(string(msgJson), logtype.GoPanic, c)
if config.IsDebug() {
//本地开发 debug 模式开启时输出错误信息到shell
syslog.Println(err)
}
c.JSON(500, gin.H{
"code": 500,
"msg": "system error",
"request_uri": c.Request.URL.Path,
"data": make(map[string]string),
})
}
}()
//before request
c.Next()
//after request
}
}

View File

@ -0,0 +1,34 @@
package middlewares
import (
"fmt"
"github.com/gin-gonic/gin"
"qteam/app/http/trace"
)
const (
componentIDGOHttpServer = 5004
)
func Trace() gin.HandlerFunc {
return func(c *gin.Context) {
tracer, err := trace.Tracer()
fmt.Println(err, "eeee", tracer)
r := c.Request
span := tracer.StartSpan("operation-name")
// 可以自定义tag
span.SetName(c.Request.Method + "---" + r.Method)
span.Tag("login", "jaja")
c.Request = c.Request.WithContext(c)
c.Next()
code := c.Writer.Status()
fmt.Println("code", code)
if code >= 400 {
span.SetName(c.Request.RequestURI + "---" + fmt.Sprintf("%s%s", r.Host, r.URL.Path))
//span.Error(time.Now(), fmt.Sprintf("Error on handling request, statusCode: %d", code))
}
span.Finish()
}
}

28
app/http/routes/admin.go Normal file
View File

@ -0,0 +1,28 @@
package routes
import (
"github.com/gin-gonic/gin"
"github.com/qit-team/snow-core/http/middleware"
"qteam/app/http/controllers"
"qteam/app/http/middlewares"
"qteam/app/http/trace"
"qteam/app/utils"
"qteam/config"
)
func RegisterAdminRoute(router *gin.Engine) {
router.Use(middlewares.ServerRecovery(), middleware.GenRequestId, middleware.GenContextKit, middleware.AccessLog())
router.NoRoute(controllers.Error404)
if len(config.GetConf().SkyWalkingOapServer) > 0 && config.IsEnvEqual(config.ProdEnv) {
err := trace.InitTracer(config.GetConf().ServiceName, config.GetConf().SkyWalkingOapServer)
if err != nil {
utils.Log(nil, "InitTracer", err.Error())
} else {
router.Use(middlewares.Trace())
}
}
//api := router.Group("/api")
{
//api.GET("/banner_list", controllers.GetBannerList)
}
}

52
app/http/routes/route.go Normal file
View File

@ -0,0 +1,52 @@
package routes
/**
* 配置路由
*/
import (
"qteam/app/http/controllers"
"qteam/app/http/middlewares"
"qteam/app/http/trace"
"qteam/app/utils/metric"
"qteam/config"
"github.com/gin-gonic/gin"
"github.com/qit-team/snow-core/http/middleware"
"github.com/qit-team/snow-core/log/logger"
"github.com/swaggo/gin-swagger"
"github.com/swaggo/gin-swagger/swaggerFiles"
)
// api路由配置
func RegisterRoute(router *gin.Engine) {
//middleware: 服务错误处理 => 生成请求id => access log
router.Use(middlewares.ServerRecovery(), middleware.GenRequestId, middleware.GenContextKit, middleware.AccessLog())
if config.GetConf().PrometheusCollectEnable && config.IsEnvEqual(config.ProdEnv) {
router.Use(middlewares.CollectMetric())
metric.Init(metric.EnableRuntime(), metric.EnableProcess())
metricHandler := metric.Handler()
router.GET("/metrics", func(ctx *gin.Context) {
metricHandler.ServeHTTP(ctx.Writer, ctx.Request)
})
}
if len(config.GetConf().SkyWalkingOapServer) > 0 && config.IsEnvEqual(config.ProdEnv) {
err := trace.InitTracer(config.GetConf().ServiceName, config.GetConf().SkyWalkingOapServer)
if err != nil {
logger.Error(nil, "InitTracer", err.Error())
} else {
router.Use(middlewares.Trace())
}
}
router.NoRoute(controllers.Error404)
//api版本
//v1 := router.Group("/v1")
{
//v1.GET("/banner_list", controllers.GetBannerList)
}
router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
}

56
app/http/trace/trace.go Normal file
View File

@ -0,0 +1,56 @@
package trace
import (
"github.com/openzipkin/zipkin-go"
zkHttp "github.com/openzipkin/zipkin-go/reporter/http"
"log"
"qteam/config"
"sync"
)
var (
tracer *zipkin.Tracer
lock sync.Mutex
)
func Tracer() (*zipkin.Tracer, error) {
if tracer == nil {
// 有err, 不适合用sync.Once做单例
lock.Lock()
defer lock.Unlock()
if tracer == nil {
err := InitTracer(config.GetConf().ServiceName, config.GetConf().SkyWalkingOapServer)
if err != nil {
return nil, err
}
}
}
return tracer, nil
}
func InitTracer(serviceName, skyWalkingOapServer string) error {
zipkinReporter := zkHttp.NewReporter(skyWalkingOapServer)
// create our local service endpoint
endpoint, err := zipkin.NewEndpoint(serviceName, "192.168.110.65:8081")
if err != nil {
log.Fatalf("unable to create local endpoint: %+v\n", err)
}
sampler := zipkin.NewModuloSampler(1)
// Initialize the tracer.
nativeTracer, err := zipkin.NewTracer(
zipkinReporter,
zipkin.WithLocalEndpoint(endpoint),
zipkin.WithSampler(sampler),
)
// initialize our tracer
//nativeTracer, err := zipkin.NewTracer(zipkinReporter, zipkin.WithLocalEndpoint(endpoint))
if err != nil {
log.Fatalf("unable to create tracer: %+v\n", err)
}
// use zipkin-go-opentracing to wrap our tracer
tracer = nativeTracer
return err
}

View File

@ -0,0 +1,66 @@
package basejob
import (
"context"
"sync"
"github.com/qit-team/work"
)
var (
jb *work.Job
register func(job *work.Job)
mu sync.RWMutex
)
func SetJob(job *work.Job) {
if jb == nil {
jb = job
}
}
func SetJobRegister(r func(*work.Job)) {
register = r
}
func GetJob() *work.Job {
if jb == nil {
if register != nil {
mu.Lock()
defer mu.Unlock()
jb = work.New()
register(jb)
} else {
panic("job register is nil")
}
}
return jb
}
/**
* 消息入队 -- 原始message
*/
func Enqueue(ctx context.Context, topic string, message string, args ...interface{}) (isOk bool, err error) {
return GetJob().Enqueue(ctx, topic, message, args...)
}
/**
* 消息入队 -- Task数据结构
*/
func EnqueueWithTask(ctx context.Context, topic string, task work.Task, args ...interface{}) (isOk bool, err error) {
return GetJob().EnqueueWithTask(ctx, topic, task, args...)
}
/**
* 消息批量入队 -- 原始message
*/
func BatchEnqueue(ctx context.Context, topic string, messages []string, args ...interface{}) (isOk bool, err error) {
return GetJob().BatchEnqueue(ctx, topic, messages, args...)
}
/**
* 消息批量入队 -- Task数据结构
*/
func BatchEnqueueWithTask(ctx context.Context, topic string, tasks []work.Task, args ...interface{}) (isOk bool, err error) {
return GetJob().BatchEnqueueWithTask(ctx, topic, tasks, args...)
}

56
app/jobs/kernel.go Normal file
View File

@ -0,0 +1,56 @@
package jobs
import (
"strings"
"qteam/app/jobs/basejob"
"qteam/config"
"github.com/qit-team/snow-core/log/logger"
"github.com/qit-team/snow-core/queue"
"github.com/qit-team/snow-core/redis"
"github.com/qit-team/work"
)
/**
* 配置队列任务
*/
func RegisterWorker(job *work.Job) {
basejob.SetJob(job)
//设置worker的任务投递回调函数
job.AddFunc("topic-test", test)
//设置worker的任务投递回调函数和并发数
job.AddFunc("topic-test1", test, 2)
//使用worker结构进行注册
job.AddWorker("topic-test2", &work.Worker{Call: work.MyWorkerFunc(test), MaxConcurrency: 1})
RegisterQueueDriver(job)
SetOptions(job)
}
/**
* 给topic注册对应的队列服务
*/
func RegisterQueueDriver(job *work.Job) {
//设置队列服务需要实现work.Queue接口的方法
q := queue.GetQueue(redis.SingletonMain, queue.DriverTypeRedis)
//针对topic设置相关的queue
job.AddQueue(q, "topic-test1", "topic-test2")
//设置默认的queue, 没有设置过的topic会使用默认的queue
job.AddQueue(q)
}
/**
* 设置配置参数
*/
func SetOptions(job *work.Job) {
//设置logger需要实现work.Logger接口的方法
job.SetLogger(logger.GetLogger())
//设置启用的topic未设置表示启用全部注册过topic
if config.GetOptions().Queue != "" {
topics := strings.Split(config.GetOptions().Queue, ",")
job.SetEnableTopics(topics...)
}
}

24
app/jobs/test.go Normal file
View File

@ -0,0 +1,24 @@
package jobs
import (
"fmt"
"time"
"github.com/qit-team/work"
)
func test(task work.Task) (work.TaskResult) {
time.Sleep(time.Millisecond * 5)
s, err := work.JsonEncode(task)
if err != nil {
//work.StateFailed 不会进行ack确认
//work.StateFailedWithAck 会进行actk确认
//return work.TaskResult{Id: task.Id, State: work.StateFailed}
return work.TaskResult{Id: task.Id, State: work.StateFailedWithAck}
} else {
//work.StateSucceed 会进行ack确认
fmt.Println("do task", s)
return work.TaskResult{Id: task.Id, State: work.StateSucceed}
}
}

0
app/utils/.gitkeep Normal file
View File

17
app/utils/des.go Normal file
View File

@ -0,0 +1,17 @@
package utils
import (
"fmt"
"github.com/forgoer/openssl"
)
func Des3Encrypt(src []byte, key string) ([]byte, error) {
dst, err := openssl.Des3ECBEncrypt(src, []byte(key), openssl.PKCS7_PADDING)
return dst, err
}
func Des3ECBDecrypt(data []byte, key string) ([]byte, error) {
dst, err := openssl.Des3ECBDecrypt(data, []byte(key), openssl.PKCS7_PADDING)
fmt.Println(string(dst))
return dst, err
}

View File

@ -0,0 +1,104 @@
package httpclient
import (
"fmt"
"qteam/app/utils"
"github.com/valyala/fasthttp"
"time"
)
func FastHttpPost(url string, header map[string]string, body []byte, timeout int) ([]byte, error) {
req := fasthttp.AcquireRequest()
defer fasthttp.ReleaseRequest(req) // 用完需要释放资源
// 默认是application/x-www-form-urlencoded
req.Header.SetMethod("POST")
for k, v := range header {
req.Header.Set(k, v)
}
req.SetRequestURI(url)
req.SetBody(body)
resp := fasthttp.AcquireResponse()
defer fasthttp.ReleaseResponse(resp) // 用完需要释放资源
var err error
if timeout > 0 {
if err = fasthttp.Do(req, resp); err != nil {
utils.Log(nil, "http请求失败", err, url)
return nil, err
}
} else {
if err := fasthttp.DoTimeout(req, resp, time.Duration(timeout)*time.Second); err != nil {
utils.Log(nil, "http请求失败", err, url)
return nil, err
}
}
b := resp.Body()
//fmt.Println(string(b),"http请求")
return b, nil
}
func FastHttpPostForm(url string, header map[string]string, body map[string]string, timeout int) ([]byte, error) {
req := fasthttp.AcquireRequest()
defer fasthttp.ReleaseRequest(req) // 用完需要释放资源
// 默认是application/x-www-form-urlencoded
req.Header.SetMethod("POST")
for k, v := range header {
req.Header.Set(k, v)
}
req.SetRequestURI(url)
args := &fasthttp.Args{}
for k, v := range body {
args.Add(k, v)
}
req.SetBody(args.QueryString())
resp := fasthttp.AcquireResponse()
defer fasthttp.ReleaseResponse(resp) // 用完需要释放资源
var err error
if timeout == 0 {
if err = fasthttp.Do(req, resp); err != nil {
utils.Log(nil, "http请求失败", err, url)
return nil, err
}
} else {
if err := fasthttp.DoTimeout(req, resp, time.Duration(timeout)*time.Second); err != nil {
utils.Log(nil, "http请求失败", err, url)
return nil, err
}
}
b := resp.Body()
return b, nil
}
func FastHttpGet(url string, header map[string]string, body map[string]string, timeout int) ([]byte, error) {
req := fasthttp.AcquireRequest()
defer fasthttp.ReleaseRequest(req) // 用完需要释放资源
// 默认是application/x-www-form-urlencoded
req.Header.SetMethod("GET")
for k, v := range header {
req.Header.Set(k, v)
}
if len(body) > 0 {
url += "?"
for k, v := range body {
url += k + "=" + v + "&"
}
url = url[0 : len(url)-1]
}
fmt.Println(url)
req.SetRequestURI(url)
resp := fasthttp.AcquireResponse()
defer fasthttp.ReleaseResponse(resp) // 用完需要释放资源
var err error
if timeout == 0 {
if err = fasthttp.Do(req, resp); err != nil {
utils.Log(nil, "http请求失败", err, url)
return nil, err
}
} else {
if err := fasthttp.DoTimeout(req, resp, time.Duration(timeout)*time.Second); err != nil {
utils.Log(nil, "http请求失败", err, url)
return nil, err
}
}
b := resp.Body()
return b, nil
}

View File

@ -0,0 +1,177 @@
package metric
// prometheus metricunique identifier: name and optional key-value pairs called labels
// 1. name regexp: [a-zA-Z_:][a-zA-Z0-9_:]*
// 2. label name regexp: [a-zA-Z_][a-zA-Z0-9_]*
// 3. Label names beginning with __ are reserved for internal use.
// 4. Label values may contain any Unicode characters.
// 5. notation: <metric name>{<label name>=<label value>, ...}
// for example: api_http_requests_total{method="POST", handler="/messages"}
// A label with an empty label value is considered equivalent to a label that does not exist.
// each sample consists of :
// - a float64 value
// - a millisecond-precision timestamp
// metric type:
// - Counter
// A cumulative metric that represents a single monotonically increasing counter whose value can only increase or be reset to zero on restart.
// - Gauge
// A gauge is a metric that represents a single numerical value that can arbitrarily go up and down.
// - Histogram
// A histogram samples observations (usually things like request durations or response sizes) and counts them in configurable buckets. It also provides a sum of all observed values.
// - Summary
// Similar to a histogram, a summary samples observations (usually things like request durations and response sizes). While it also provides a total count of observations and a sum of all observed values, it calculates configurable quantiles over a sliding time window.
// metric:
// Counter:
// - req_total_count
// - req_failed_count
// Gauge:
// - heap_inuse_size
// - heap_total_size
// - heap_object_num
// - goroutine_num
// Histogram:
// - req_cost_time
// Summary:
// - req_cost_time
import (
"net/http"
"sync"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
const (
//ENV = "env"
APP = "snow"
VER = "ver"
)
var (
collectors = []prometheus.Collector{}
)
func RegisterCollector(c ...prometheus.Collector) {
collectors = append(collectors, c...)
}
type Options struct {
labels map[string]string
processEnable bool
runtimeEnable bool
}
type Option func(opt *Options)
// 添加App和Ver label
func AppVer(app, ver string) Option {
return func(opt *Options) {
if app != "" {
opt.labels[APP] = app
}
if ver != "" {
opt.labels[VER] = ver
}
}
}
// 添加额外label
func WithLabel(key, val string) Option {
return func(opt *Options) {
if key != "" && val != "" {
opt.labels[key] = val
}
}
}
// 收集进程信息
func EnableProcess() Option {
return func(opt *Options) {
opt.processEnable = true
}
}
func EnableRuntime() Option {
return func(opt *Options) {
opt.runtimeEnable = true
}
}
type Reporter struct {
opts Options
collectors []prometheus.Collector
// registerer
registerer prometheus.Registerer
gatherer prometheus.Gatherer
}
var (
once sync.Once
reporter Reporter
)
func Init(opts ...Option) {
_opts := Options{
labels: map[string]string{},
}
for _, opt := range opts {
opt(&_opts)
}
once.Do(func() {
cs := collectors
if _opts.processEnable {
cs = append(cs, prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}))
}
if _opts.runtimeEnable {
cs = append(cs, prometheus.NewGoCollector())
}
reporter = Reporter{
opts: _opts,
collectors: cs,
}
registry := prometheus.NewRegistry()
reporter.registerer = prometheus.WrapRegistererWith(reporter.opts.labels, registry)
reporter.gatherer = registry
reporter.registerer.MustRegister(reporter.collectors...)
})
}
func (p *Reporter) newCounterVec(metric string, labels []string) *prometheus.CounterVec {
counterVec := prometheus.NewCounterVec(prometheus.CounterOpts{
Name: metric,
}, labels)
return counterVec
}
func (p *Reporter) newGaugeVec(metric string, labels []string) *prometheus.GaugeVec {
gaugeVec := prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: metric,
}, labels)
return gaugeVec
}
func (p *Reporter) newHistogramVec(metric string, labels []string, buckets []float64) *prometheus.HistogramVec {
histogramVec := prometheus.NewHistogramVec(prometheus.HistogramOpts{
Name: metric,
Buckets: buckets,
}, labels)
return histogramVec
}
func Handler() http.Handler {
return promhttp.InstrumentMetricHandler(
reporter.registerer, promhttp.HandlerFor(reporter.gatherer, promhttp.HandlerOpts{}),
)
}

48
app/utils/nacos.go Normal file
View File

@ -0,0 +1,48 @@
package utils
import (
"fmt"
"github.com/nacos-group/nacos-sdk-go/v2/clients"
"github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
"qteam/config"
"sync"
)
var (
client naming_client.INamingClient
once sync.Once
)
func GetNaocosClient() naming_client.INamingClient {
once.Do(func() {
//注册nacos
fmt.Println(config.GetConf().Nacos.Port)
sc := []constant.ServerConfig{
*constant.NewServerConfig(config.GetConf().Nacos.Url, uint64(config.GetConf().Nacos.Port), constant.WithContextPath("/nacos")),
}
//create ClientConfig
cc := *constant.NewClientConfig(
constant.WithNamespaceId(""),
constant.WithTimeoutMs(5000),
constant.WithNotLoadCacheAtStart(true),
constant.WithLogDir("/tmp/nacos/log"),
constant.WithCacheDir("/tmp/nacos/cache"),
constant.WithLogLevel("debug"),
)
var err error
// create naming client
client, err = clients.NewNamingClient(
vo.NacosClientParam{
ClientConfig: &cc,
ServerConfigs: sc,
},
)
if err != nil {
panic(err)
}
})
return client
}

81
app/utils/util.go Normal file
View File

@ -0,0 +1,81 @@
package utils
import (
"context"
"crypto/md5"
"encoding/hex"
"fmt"
"github.com/gin-gonic/gin"
"github.com/qit-team/snow-core/redis"
"net"
"qteam/config"
"runtime"
"strconv"
"strings"
"time"
)
const (
CODE62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
codeLen = 20
)
func GetHostIp() string {
conn, err := net.Dial("udp", "8.8.8.8:53")
if err != nil {
fmt.Println("get current host ip err: ", err)
return ""
}
addr := conn.LocalAddr().(*net.UDPAddr)
ip := strings.Split(addr.String(), ":")[0]
return ip
}
func Log(c *gin.Context, name string, msg ...interface{}) {
_, file, line, _ := runtime.Caller(1)
timeLayout := "2006-01-01 03:04:05" //转化所需模板
var datetime = time.Unix(time.Now().Unix(), 0).Format(timeLayout)
fmt.Println(name, msg, file, line, datetime)
}
func GetRealKey(key string) string {
return config.GetConf().ServiceName + ":" + key
}
// MD5加密
func SToMd5(data string) string {
r := md5.Sum([]byte(data))
return hex.EncodeToString(r[:])
}
/**
* 编码 整数 base62 字符串
*/
func Encode(number int64) string {
if number == 0 {
return "0"
}
result := make([]byte, 0)
for number > 0 {
round := number / codeLen
remain := number % codeLen
result = append(result, CODE62[remain])
number = round
}
return string(result)
}
// 生成用户touken
func GeneratorToken(playerName string, playerId string) string {
//去生成一个token返回给客户端
m5 := SToMd5(playerName + time.Now().String())
var pid, _ = strconv.ParseInt(playerId, 10, 64)
bsCode := Encode(pid)
tk := m5 + bsCode
//将token放入redis
_, err := redis.GetRedis(redis.SingletonMain).SetEX(context.Background(), GetRealKey(tk), playerId, 3600).Result()
if err != nil {
Log(nil, "token", err)
}
return tk
}

80
bootstrap/bootstrap.go Normal file
View File

@ -0,0 +1,80 @@
package bootstrap
import (
"qteam/app/jobs"
"qteam/app/jobs/basejob"
"qteam/app/utils"
"qteam/config"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
"github.com/qit-team/snow-core/db"
"github.com/qit-team/snow-core/kernel/close"
"github.com/qit-team/snow-core/kernel/container"
"github.com/qit-team/snow-core/log/accesslogger"
"github.com/qit-team/snow-core/log/logger"
"github.com/qit-team/snow-core/redis"
)
// 全局变量
var App *container.Container
/**
* 服务引导程序
*/
func Bootstrap(conf *config.Config) (err error) {
//容器
App = container.App
//注册db服务
//第一个参数为注入别名,第二个参数为配置,第三个参数可选为是否懒加载
err = db.Pr.Register(db.SingletonMain, conf.Db)
if err != nil {
return
}
//注册redis服务
err = redis.Pr.Register(redis.SingletonMain, conf.Redis)
if err != nil {
return
}
//注册mns服务
//err = alimns.Pr.Register(alimns.SingletonMain, conf.Mns, true)
//if err != nil {
// return
//}
//注册日志类服务
err = logger.Pr.Register(logger.SingletonMain, conf.Log, true)
if err != nil {
return
}
//注册access log服务
err = accesslogger.Pr.Register(accesslogger.SingletonMain, conf.Log)
if err != nil {
return
}
//注册应用停止时调用的关闭服务
close.MultiRegister(db.Pr, redis.Pr)
//Register
_, err = utils.GetNaocosClient().RegisterInstance(vo.RegisterInstanceParam{
Ip: utils.GetHostIp(),
Port: uint64(conf.Api.Port),
ServiceName: "snow",
GroupName: "group-d",
ClusterName: "cluster-snow",
Weight: 10,
Enable: true,
Healthy: true,
Ephemeral: true,
Metadata: map[string]string{},
})
utils.Log(nil, "nacos err", err)
//注册job register为了非job模式的消息入队调用
basejob.SetJobRegister(jobs.RegisterWorker)
return nil
}

2
build/bin/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

9
build/shell/build.sh Normal file
View File

@ -0,0 +1,9 @@
#/bin/bash
os=$1 #系统linux
arch=$2 #架构amd64
#回到根目录
rootPath=$(cd `dirname $0`/../../; pwd)
#编译
GOOS=$os GOARCH=$arch go build -o build/bin/snow main.go

85
config/config.go Normal file
View File

@ -0,0 +1,85 @@
package config
import (
"os"
"github.com/BurntSushi/toml"
"github.com/qit-team/snow-core/config"
)
const (
ProdEnv = "production" //线上环境
BetaEnv = "beta" //beta环境
DevEnv = "develop" //开发环境
LocalEnv = "local" //本地环境
)
var srvConf *Config
// ------------------------配置文件解析
type Config struct {
ServiceName string `toml:"ServiceName"`
Env string `toml:"Env"`
Debug bool `toml:"Debug"`
PrometheusCollectEnable bool `toml:"PrometheusCollectEnable"`
SkyWalkingOapServer string `toml:"SkyWalkingOapServer"`
Log config.LogConfig `toml:"Log"`
Redis config.RedisConfig `toml:"Redis"`
Mns config.MnsConfig `toml:"AliMns"`
Db config.DbConfig `toml:"Db"`
Api config.ApiConfig `toml:"Api"`
Admin config.ApiConfig `toml:"Admin"`
Nacos Nacos `toml:"Nacas"`
Rpc Rpc `toml:"Rpc"`
AppKey string `toml:"AppKey"`
}
type Rpc struct {
User string
}
type Nacos struct {
Url string
Port int64
}
func newConfig() *Config {
return new(Config)
}
// ------------------------ 加载配置 ------------------------//
func Load(path string) (*Config, error) {
_, err := os.Stat(path)
if err != nil {
return nil, err
}
conf := newConfig()
if _, err := toml.DecodeFile(path, conf); err != nil {
return nil, err
}
srvConf = conf
return conf, nil
}
// 当前配置
func GetConf() *Config {
return srvConf
}
// 是否调试模式
func IsDebug() bool {
return srvConf.Debug
}
// 当前环境,默认本地开发
func GetEnv() string {
if srvConf.Env == "" {
return LocalEnv
}
return srvConf.Env
}
// 是否当前环境
func IsEnvEqual(env string) bool {
return GetEnv() == env
}

45
config/option.go Normal file
View File

@ -0,0 +1,45 @@
package config
import (
"flag"
"strings"
)
var options *Options
//------------------------启动命令配置
type Options struct {
ShowVersion bool
Cmd string
ConfFile string
App string
PidDir string
Queue string
Command string
}
func parseOptions() *Options {
opts := new(Options)
flag.BoolVar(&opts.ShowVersion, "v", false, "show version")
flag.StringVar(&opts.App, "a", "api", "application to run")
flag.StringVar(&opts.Cmd, "k", "", "status|stop|restart")
flag.StringVar(&opts.ConfFile, "c", ".env", "conf file path")
flag.StringVar(&opts.PidDir, "p", "/var/run/", "pid directory")
flag.StringVar(&opts.Queue, "queue", "", "topic of queue is enable")
flag.StringVar(&opts.Command, "m", "", "command name")
flag.Parse()
return opts
}
//获取启动命令配置
func GetOptions() *Options {
if options == nil {
options = parseOptions()
}
return options
}
//pid进程号的保存路径
func (opts *Options) GenPidFile() string {
return strings.TrimRight(opts.PidDir, "/") + "/" + opts.App + ".pid"
}

342
docs/docs.go Normal file
View File

@ -0,0 +1,342 @@
// Package docs Code generated by swaggo/swag. DO NOT EDIT
package docs
import "github.com/swaggo/swag"
const docTemplate = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
"description": "{{escape .Description}}",
"title": "{{.Title}}",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"name": "API Support",
"url": "http://www.swagger.io/support",
"email": "support@swagger.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "{{.Version}}"
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/test": {
"post": {
"description": "request和response的示例",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"snow"
],
"summary": "request和response的示例",
"parameters": [
{
"description": "test request",
"name": "test",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/entities.TestRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/entities.TestResponse"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/controllers.HTTPError"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/controllers.HTTPError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/controllers.HTTPError"
}
}
}
}
},
"/test_validator": {
"post": {
"description": "HandleTestValidator的示例",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"snow"
],
"summary": "HandleTestValidator的示例",
"parameters": [
{
"description": "example of validator",
"name": "testValidator",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/entities.TestValidatorRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/entities.TestValidatorRequest"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/controllers.HTTPError"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/controllers.HTTPError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/controllers.HTTPError"
}
}
}
}
}
},
"definitions": {
"controllers.HTTPError": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"example": 400
},
"message": {
"type": "string",
"example": "status bad request"
}
}
},
"entities.Address": {
"type": "object",
"required": [
"city",
"phone",
"planet",
"street"
],
"properties": {
"city": {
"type": "string",
"example": "xiamen"
},
"phone": {
"type": "string",
"example": "snow"
},
"planet": {
"type": "string",
"example": "snow"
},
"street": {
"type": "string",
"example": "huandaodonglu"
}
}
},
"entities.TestRequest": {
"type": "object",
"properties": {
"name": {
"type": "string",
"example": "snow"
},
"url": {
"type": "string",
"example": "github.com/qit-team/snow"
}
}
},
"entities.TestResponse": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"example": 1
},
"name": {
"type": "string",
"example": "snow"
},
"url": {
"type": "string",
"example": "github.com/qit-team/snow"
}
}
},
"entities.TestValidatorRequest": {
"type": "object",
"required": [
"addresses",
"age",
"email",
"id",
"mobile",
"name",
"test_num",
"url"
],
"properties": {
"addresses": {
"type": "array",
"items": {
"$ref": "#/definitions/entities.Address"
}
},
"age": {
"type": "integer",
"maximum": 130,
"minimum": 0,
"example": 20
},
"content": {
"type": "string",
"example": "snow"
},
"email": {
"type": "string",
"example": "snow@github.com"
},
"id": {
"description": "tips因为组件required不管是没传值或者传 0 or \"\" 都通过不了但是如果用指针类型那么0就是0而nil无法通过校验",
"type": "integer",
"example": 1
},
"mobile": {
"type": "string",
"example": "snow"
},
"name": {
"type": "string",
"example": "snow"
},
"range_num": {
"type": "integer",
"maximum": 10,
"minimum": 1,
"example": 3
},
"test_num": {
"type": "integer",
"enum": [
5,
7,
9
],
"example": 7
},
"url": {
"type": "string",
"example": "github.com/qit-team/snow"
}
}
}
},
"securityDefinitions": {
"ApiKeyAuth": {
"type": "apiKey",
"name": "Authorization",
"in": "header"
},
"BasicAuth": {
"type": "basic"
},
"OAuth2AccessCode": {
"type": "oauth2",
"flow": "accessCode",
"authorizationUrl": "https://example.com/oauth/authorize",
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"admin": "Grants read and write access to administrative information"
}
},
"OAuth2Application": {
"type": "oauth2",
"flow": "application",
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"admin": "Grants read and write access to administrative information",
"write": "Grants write access"
}
},
"OAuth2Implicit": {
"type": "oauth2",
"flow": "implicit",
"authorizationUrl": "https://example.com/oauth/authorize",
"scopes": {
"admin": "Grants read and write access to administrative information",
"write": "Grants write access"
}
},
"OAuth2Password": {
"type": "oauth2",
"flow": "password",
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"admin": "Grants read and write access to administrative information",
"read": "Grants read access",
"write": "Grants write access"
}
}
}
}`
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
Version: "1.0",
Host: "localhost:8080",
BasePath: "/",
Schemes: []string{},
Title: "Swagger Example API",
Description: "This is a sample server celler server.",
InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
//LeftDelim: "{{",
//RightDelim: "}}",
}
func init() {
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}

318
docs/swagger.json Normal file
View File

@ -0,0 +1,318 @@
{
"swagger": "2.0",
"info": {
"description": "This is a sample server celler server.",
"title": "Swagger Example API",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"name": "API Support",
"url": "http://www.swagger.io/support",
"email": "support@swagger.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "1.0"
},
"host": "localhost:8080",
"basePath": "/",
"paths": {
"/test": {
"post": {
"description": "request和response的示例",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"snow"
],
"summary": "request和response的示例",
"parameters": [
{
"description": "test request",
"name": "test",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/entities.TestRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/entities.TestResponse"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/controllers.HTTPError"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/controllers.HTTPError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/controllers.HTTPError"
}
}
}
}
},
"/test_validator": {
"post": {
"description": "HandleTestValidator的示例",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"snow"
],
"summary": "HandleTestValidator的示例",
"parameters": [
{
"description": "example of validator",
"name": "testValidator",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/entities.TestValidatorRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/entities.TestValidatorRequest"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/controllers.HTTPError"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/controllers.HTTPError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/controllers.HTTPError"
}
}
}
}
}
},
"definitions": {
"controllers.HTTPError": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"example": 400
},
"message": {
"type": "string",
"example": "status bad request"
}
}
},
"entities.Address": {
"type": "object",
"required": [
"city",
"phone",
"planet",
"street"
],
"properties": {
"city": {
"type": "string",
"example": "xiamen"
},
"phone": {
"type": "string",
"example": "snow"
},
"planet": {
"type": "string",
"example": "snow"
},
"street": {
"type": "string",
"example": "huandaodonglu"
}
}
},
"entities.TestRequest": {
"type": "object",
"properties": {
"name": {
"type": "string",
"example": "snow"
},
"url": {
"type": "string",
"example": "github.com/qit-team/snow"
}
}
},
"entities.TestResponse": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"example": 1
},
"name": {
"type": "string",
"example": "snow"
},
"url": {
"type": "string",
"example": "github.com/qit-team/snow"
}
}
},
"entities.TestValidatorRequest": {
"type": "object",
"required": [
"addresses",
"age",
"email",
"id",
"mobile",
"name",
"test_num",
"url"
],
"properties": {
"addresses": {
"type": "array",
"items": {
"$ref": "#/definitions/entities.Address"
}
},
"age": {
"type": "integer",
"maximum": 130,
"minimum": 0,
"example": 20
},
"content": {
"type": "string",
"example": "snow"
},
"email": {
"type": "string",
"example": "snow@github.com"
},
"id": {
"description": "tips因为组件required不管是没传值或者传 0 or \"\" 都通过不了但是如果用指针类型那么0就是0而nil无法通过校验",
"type": "integer",
"example": 1
},
"mobile": {
"type": "string",
"example": "snow"
},
"name": {
"type": "string",
"example": "snow"
},
"range_num": {
"type": "integer",
"maximum": 10,
"minimum": 1,
"example": 3
},
"test_num": {
"type": "integer",
"enum": [
5,
7,
9
],
"example": 7
},
"url": {
"type": "string",
"example": "github.com/qit-team/snow"
}
}
}
},
"securityDefinitions": {
"ApiKeyAuth": {
"type": "apiKey",
"name": "Authorization",
"in": "header"
},
"BasicAuth": {
"type": "basic"
},
"OAuth2AccessCode": {
"type": "oauth2",
"flow": "accessCode",
"authorizationUrl": "https://example.com/oauth/authorize",
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"admin": "Grants read and write access to administrative information"
}
},
"OAuth2Application": {
"type": "oauth2",
"flow": "application",
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"admin": "Grants read and write access to administrative information",
"write": "Grants write access"
}
},
"OAuth2Implicit": {
"type": "oauth2",
"flow": "implicit",
"authorizationUrl": "https://example.com/oauth/authorize",
"scopes": {
"admin": "Grants read and write access to administrative information",
"write": "Grants write access"
}
},
"OAuth2Password": {
"type": "oauth2",
"flow": "password",
"tokenUrl": "https://example.com/oauth/token",
"scopes": {
"admin": "Grants read and write access to administrative information",
"read": "Grants read access",
"write": "Grants write access"
}
}
}
}

227
docs/swagger.yaml Normal file
View File

@ -0,0 +1,227 @@
basePath: /
definitions:
controllers.HTTPError:
properties:
code:
example: 400
type: integer
message:
example: status bad request
type: string
type: object
entities.Address:
properties:
city:
example: xiamen
type: string
phone:
example: snow
type: string
planet:
example: snow
type: string
street:
example: huandaodonglu
type: string
required:
- city
- phone
- planet
- street
type: object
entities.TestRequest:
properties:
name:
example: snow
type: string
url:
example: github.com/qit-team/snow
type: string
type: object
entities.TestResponse:
properties:
id:
example: 1
type: integer
name:
example: snow
type: string
url:
example: github.com/qit-team/snow
type: string
type: object
entities.TestValidatorRequest:
properties:
addresses:
items:
$ref: '#/definitions/entities.Address'
type: array
age:
example: 20
maximum: 130
minimum: 0
type: integer
content:
example: snow
type: string
email:
example: snow@github.com
type: string
id:
description: tips因为组件required不管是没传值或者传 0 or "" 都通过不了但是如果用指针类型那么0就是0而nil无法通过校验
example: 1
type: integer
mobile:
example: snow
type: string
name:
example: snow
type: string
range_num:
example: 3
maximum: 10
minimum: 1
type: integer
test_num:
enum:
- 5
- 7
- 9
example: 7
type: integer
url:
example: github.com/qit-team/snow
type: string
required:
- addresses
- age
- email
- id
- mobile
- name
- test_num
- url
type: object
host: localhost:8080
info:
contact:
email: support@swagger.io
name: API Support
url: http://www.swagger.io/support
description: This is a sample server celler server.
license:
name: Apache 2.0
url: http://www.apache.org/licenses/LICENSE-2.0.html
termsOfService: http://swagger.io/terms/
title: Swagger Example API
version: "1.0"
paths:
/test:
post:
consumes:
- application/json
description: request和response的示例
parameters:
- description: test request
in: body
name: test
required: true
schema:
$ref: '#/definitions/entities.TestRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/entities.TestResponse'
type: array
"400":
description: Bad Request
schema:
$ref: '#/definitions/controllers.HTTPError'
"404":
description: Not Found
schema:
$ref: '#/definitions/controllers.HTTPError'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/controllers.HTTPError'
summary: request和response的示例
tags:
- snow
/test_validator:
post:
consumes:
- application/json
description: HandleTestValidator的示例
parameters:
- description: example of validator
in: body
name: testValidator
required: true
schema:
$ref: '#/definitions/entities.TestValidatorRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/entities.TestValidatorRequest'
type: array
"400":
description: Bad Request
schema:
$ref: '#/definitions/controllers.HTTPError'
"404":
description: Not Found
schema:
$ref: '#/definitions/controllers.HTTPError'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/controllers.HTTPError'
summary: HandleTestValidator的示例
tags:
- snow
securityDefinitions:
ApiKeyAuth:
in: header
name: Authorization
type: apiKey
BasicAuth:
type: basic
OAuth2AccessCode:
authorizationUrl: https://example.com/oauth/authorize
flow: accessCode
scopes:
admin: Grants read and write access to administrative information
tokenUrl: https://example.com/oauth/token
type: oauth2
OAuth2Application:
flow: application
scopes:
admin: Grants read and write access to administrative information
write: Grants write access
tokenUrl: https://example.com/oauth/token
type: oauth2
OAuth2Implicit:
authorizationUrl: https://example.com/oauth/authorize
flow: implicit
scopes:
admin: Grants read and write access to administrative information
write: Grants write access
type: oauth2
OAuth2Password:
flow: password
scopes:
admin: Grants read and write access to administrative information
read: Grants read access
write: Grants write access
tokenUrl: https://example.com/oauth/token
type: oauth2
swagger: "2.0"

34
event/EventManger.go Normal file
View File

@ -0,0 +1,34 @@
package event
import "qteam/app/utils"
type EventHandler interface {
Handle(param interface{})
}
type EventManagerFactory struct {
handers map[string][]EventHandler
}
func (this *EventManagerFactory) Register(eventName string, handler EventHandler) {
if this.handers == nil {
this.handers = make(map[string][]EventHandler)
}
this.handers[eventName] = append(this.handers[eventName], handler)
}
func (this *EventManagerFactory) TrigerEvent(eventName string, param interface{}) {
defer func() {
err := recover()
if err != nil {
utils.Log(nil, "event err", err)
}
}()
for _, v := range this.handers[eventName] {
v.Handle(param)
}
}

18
event/event.go Normal file
View File

@ -0,0 +1,18 @@
package event
import (
"qteam/app/constants/common"
"qteam/event/observers"
)
/**
*事件触发
event.EventManger.TrigerEvent(common.Event_USER_LOG_IN, param)
*/
var EventManger *EventManagerFactory
func init() {
EventManger = new(EventManagerFactory)
EventManger.Register(common.Event_USER_LOG_IN, &observers.ScoreLogin{})
}

8
event/observers/score.go Normal file
View File

@ -0,0 +1,8 @@
package observers
type ScoreLogin struct {
}
func (this *ScoreLogin) Handle(param interface{}) {
}

25
go.mod Normal file
View File

@ -0,0 +1,25 @@
module qteam
go 1.12
require (
github.com/BurntSushi/toml v0.4.1
github.com/forgoer/openssl v1.6.0
github.com/gin-gonic/gin v1.7.7
github.com/go-playground/locales v0.14.0
github.com/go-playground/universal-translator v0.18.0
github.com/go-sql-driver/mysql v1.6.0
github.com/mailru/easyjson v0.7.7 // indirect
github.com/nacos-group/nacos-sdk-go/v2 v2.2.5
github.com/openzipkin/zipkin-go v0.2.2
github.com/prometheus/client_golang v1.12.2
github.com/qit-team/snow-core v0.1.28
github.com/qit-team/work v0.3.11
github.com/robfig/cron v1.2.0
github.com/swaggo/gin-swagger v1.3.3
github.com/swaggo/swag v1.7.9
github.com/valyala/fasthttp v1.31.0
google.golang.org/grpc v1.56.3
google.golang.org/protobuf v1.30.0
gopkg.in/go-playground/validator.v9 v9.31.0
)

2376
go.sum Normal file

File diff suppressed because it is too large Load Diff

2
logs/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

125
main.go Normal file
View File

@ -0,0 +1,125 @@
package main
import (
"errors"
"fmt"
"os"
"qteam/app/console"
"qteam/app/http/routes"
"qteam/app/jobs"
"qteam/bootstrap"
"qteam/config"
_ "qteam/docs"
"qteam/rpc"
_ "github.com/go-sql-driver/mysql"
_ "github.com/qit-team/snow-core/cache/rediscache"
"github.com/qit-team/snow-core/kernel/server"
_ "github.com/qit-team/snow-core/queue/redisqueue"
)
// @title Swagger Example API
// @version 1.0
// @description This is a sample server celler server.
// @termsOfService http://swagger.io/terms/
// @contact.name API Support
// @contact.url http://www.swagger.io/support
// @contact.email support@swagger.io
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @host localhost:8080
// @BasePath /
// @securityDefinitions.basic BasicAuth
// @securityDefinitions.apikey ApiKeyAuth
// @in header
// @name Authorization
// @securitydefinitions.oauth2.application OAuth2Application
// @tokenUrl https://example.com/oauth/token
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.implicit OAuth2Implicit
// @authorizationUrl https://example.com/oauth/authorize
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.password OAuth2Password
// @tokenUrl https://example.com/oauth/token
// @scope.read Grants read access
// @scope.write Grants write access
// @scope.admin Grants read and write access to administrative information
// @securitydefinitions.oauth2.accessCode OAuth2AccessCode
// @tokenUrl https://example.com/oauth/token
// @authorizationUrl https://example.com/oauth/authorize
// @scope.admin Grants read and write access to administrative information
func main() {
//解析启动命令
opts := config.GetOptions()
if opts.ShowVersion {
fmt.Printf("%s\ncommit %s\nbuilt on %s\n", server.Version, server.BuildCommit, server.BuildDate)
os.Exit(0)
}
handleCmd(opts)
err := startServer(opts)
if err != nil {
fmt.Printf("server start error, %s\n", err)
os.Exit(1)
}
}
// 执行(status|stop|restart)命令
func handleCmd(opts *config.Options) {
if opts.Cmd != "" {
pidFile := opts.GenPidFile()
err := server.HandleUserCmd(opts.Cmd, pidFile)
if err != nil {
fmt.Printf("Handle user command(%s) error, %s\n", opts.Cmd, err)
} else {
fmt.Printf("Handle user command(%s) succ \n ", opts.Cmd)
}
os.Exit(0)
}
}
func startServer(opts *config.Options) (err error) {
//加载配置
conf, err := config.Load(opts.ConfFile)
if err != nil {
return
}
//引导程序
err = bootstrap.Bootstrap(conf)
if err != nil {
return
}
pidFile := opts.GenPidFile()
//根据启动命令行参数,决定启动哪种服务模式
switch opts.App {
case "api":
err = server.StartHttp(pidFile, conf.Api, routes.RegisterRoute)
case "cron":
err = server.StartConsole(pidFile, console.RegisterSchedule)
case "job":
err = server.StartJob(pidFile, jobs.RegisterWorker)
case "command":
err = server.ExecuteCommand(opts.Command, console.RegisterCommand)
case "rpc":
err = rpc.StartRpc()
case "admin":
err = server.StartHttp(pidFile, conf.Admin, routes.RegisterAdminRoute)
default:
err = errors.New("no server start")
}
return
}

16
rpc/server.go Normal file
View File

@ -0,0 +1,16 @@
package rpc
import (
"github.com/qit-team/snow-core/kernel/server"
__ "qteam/rpc/user"
)
func StartRpc() error {
go (&__.UserServer{}).StartServer()
//等待停止信号
server.WaitStop()
return nil
}

290
rpc/user/user.pb.go Normal file
View File

@ -0,0 +1,290 @@
// 文件位置pbfile/studnet.proto
// 指定使用的语法格式根据自己下载的protoc的版本选择
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc v3.19.5
// source: user/user.proto
// 指定包名
package __
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type User struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Age int32 `protobuf:"varint,2,opt,name=age,proto3" json:"age,omitempty"`
}
func (x *User) Reset() {
*x = User{}
if protoimpl.UnsafeEnabled {
mi := &file_user_user_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *User) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*User) ProtoMessage() {}
func (x *User) ProtoReflect() protoreflect.Message {
mi := &file_user_user_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use User.ProtoReflect.Descriptor instead.
func (*User) Descriptor() ([]byte, []int) {
return file_user_user_proto_rawDescGZIP(), []int{0}
}
func (x *User) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *User) GetAge() int32 {
if x != nil {
return x.Age
}
return 0
}
// 定义请求体结构
type UserRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Number int32 `protobuf:"varint,1,opt,name=number,proto3" json:"number,omitempty"` //请求中传入学生的学号1表示占位符
}
func (x *UserRequest) Reset() {
*x = UserRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_user_user_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *UserRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UserRequest) ProtoMessage() {}
func (x *UserRequest) ProtoReflect() protoreflect.Message {
mi := &file_user_user_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UserRequest.ProtoReflect.Descriptor instead.
func (*UserRequest) Descriptor() ([]byte, []int) {
return file_user_user_proto_rawDescGZIP(), []int{1}
}
func (x *UserRequest) GetNumber() int32 {
if x != nil {
return x.Number
}
return 0
}
// 定义响应体结构
type UserResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Student *User `protobuf:"bytes,1,opt,name=student,proto3" json:"student,omitempty"`
}
func (x *UserResponse) Reset() {
*x = UserResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_user_user_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *UserResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UserResponse) ProtoMessage() {}
func (x *UserResponse) ProtoReflect() protoreflect.Message {
mi := &file_user_user_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UserResponse.ProtoReflect.Descriptor instead.
func (*UserResponse) Descriptor() ([]byte, []int) {
return file_user_user_proto_rawDescGZIP(), []int{2}
}
func (x *UserResponse) GetStudent() *User {
if x != nil {
return x.Student
}
return nil
}
var File_user_user_proto protoreflect.FileDescriptor
var file_user_user_proto_rawDesc = []byte{
0x0a, 0x0f, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x12, 0x04, 0x75, 0x73, 0x65, 0x72, 0x22, 0x2c, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12,
0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05,
0x52, 0x03, 0x61, 0x67, 0x65, 0x22, 0x25, 0x0a, 0x0b, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01,
0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x34, 0x0a, 0x0c,
0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x07,
0x73, 0x74, 0x75, 0x64, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e,
0x75, 0x73, 0x65, 0x72, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x07, 0x73, 0x74, 0x75, 0x64, 0x65,
0x6e, 0x74, 0x32, 0x4a, 0x0a, 0x0b, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
0x65, 0x12, 0x3b, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x42, 0x79, 0x53, 0x74,
0x75, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x11, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x55,
0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x75, 0x73, 0x65,
0x72, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x04,
0x5a, 0x02, 0x2e, 0x2f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_user_user_proto_rawDescOnce sync.Once
file_user_user_proto_rawDescData = file_user_user_proto_rawDesc
)
func file_user_user_proto_rawDescGZIP() []byte {
file_user_user_proto_rawDescOnce.Do(func() {
file_user_user_proto_rawDescData = protoimpl.X.CompressGZIP(file_user_user_proto_rawDescData)
})
return file_user_user_proto_rawDescData
}
var file_user_user_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_user_user_proto_goTypes = []interface{}{
(*User)(nil), // 0: user.User
(*UserRequest)(nil), // 1: user.UserRequest
(*UserResponse)(nil), // 2: user.UserResponse
}
var file_user_user_proto_depIdxs = []int32{
0, // 0: user.UserResponse.student:type_name -> user.User
1, // 1: user.UserService.GetUserByStuNumber:input_type -> user.UserRequest
2, // 2: user.UserService.GetUserByStuNumber:output_type -> user.UserResponse
2, // [2:3] is the sub-list for method output_type
1, // [1:2] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_user_user_proto_init() }
func file_user_user_proto_init() {
if File_user_user_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_user_user_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*User); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_user_user_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*UserRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_user_user_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*UserResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_user_user_proto_rawDesc,
NumEnums: 0,
NumMessages: 3,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_user_user_proto_goTypes,
DependencyIndexes: file_user_user_proto_depIdxs,
MessageInfos: file_user_user_proto_msgTypes,
}.Build()
File_user_user_proto = out.File
file_user_user_proto_rawDesc = nil
file_user_user_proto_goTypes = nil
file_user_user_proto_depIdxs = nil
}

26
rpc/user/user.proto Normal file
View File

@ -0,0 +1,26 @@
// pbfile/studnet.proto
// 使protoc的版本选择
syntax = "proto3";
//
option go_package = "./";
//
package user;
message User{
string name = 1;
int32 age = 2;
}
//
message UserRequest{
int32 number = 1; //1
}
//
message UserResponse{
User student = 1;
}
//,
service UserService{
rpc GetUserByStuNumber(UserRequest) returns (UserResponse);
}

42
rpc/user/user.server.go Normal file
View File

@ -0,0 +1,42 @@
package __
import (
"context"
"google.golang.org/grpc"
"log"
"net"
"qteam/app/utils"
"qteam/config"
)
// 服务定义
type UserServer struct{}
func (s *UserServer) GetUserByStuNumber(ctx context.Context, request *UserRequest) (*UserResponse, error) {
return &UserResponse{}, nil
}
func (s *UserServer) mustEmbedUnimplementedUserServiceServer() {
//TODO implement me
//panic("implement me")
}
func (s *UserServer) StartServer() *grpc.Server {
server := grpc.NewServer()
//注册服务
RegisterUserServiceServer(server, &UserServer{})
//启动监听程序
listener, err := net.Listen("tcp", config.GetConf().Rpc.User)
if err != nil {
log.Fatal("启动监听失败", err)
}
err = server.Serve(listener)
if err != nil {
log.Fatal("启动服务失败", err)
}
utils.Log(nil, "服务启动成功", config.GetConf().Rpc.User)
return server
}

105
rpc/user/user_grpc.pb.go Normal file
View File

@ -0,0 +1,105 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc v3.19.5
// source: user/user.proto
package __
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// UserServiceClient is the client API for UserService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type UserServiceClient interface {
GetUserByStuNumber(ctx context.Context, in *UserRequest, opts ...grpc.CallOption) (*UserResponse, error)
}
type userServiceClient struct {
cc grpc.ClientConnInterface
}
func NewUserServiceClient(cc grpc.ClientConnInterface) UserServiceClient {
return &userServiceClient{cc}
}
func (c *userServiceClient) GetUserByStuNumber(ctx context.Context, in *UserRequest, opts ...grpc.CallOption) (*UserResponse, error) {
out := new(UserResponse)
err := c.cc.Invoke(ctx, "/user.UserService/GetUserByStuNumber", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// UserServiceServer is the server API for UserService service.
// All implementations must embed UnimplementedUserServiceServer
// for forward compatibility
type UserServiceServer interface {
GetUserByStuNumber(context.Context, *UserRequest) (*UserResponse, error)
mustEmbedUnimplementedUserServiceServer()
}
// UnimplementedUserServiceServer must be embedded to have forward compatible implementations.
type UnimplementedUserServiceServer struct {
}
func (UnimplementedUserServiceServer) GetUserByStuNumber(context.Context, *UserRequest) (*UserResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetUserByStuNumber not implemented")
}
func (UnimplementedUserServiceServer) mustEmbedUnimplementedUserServiceServer() {}
// UnsafeUserServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to UserServiceServer will
// result in compilation errors.
type UnsafeUserServiceServer interface {
mustEmbedUnimplementedUserServiceServer()
}
func RegisterUserServiceServer(s grpc.ServiceRegistrar, srv UserServiceServer) {
s.RegisterService(&UserService_ServiceDesc, srv)
}
func _UserService_GetUserByStuNumber_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UserRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserServiceServer).GetUserByStuNumber(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/user.UserService/GetUserByStuNumber",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserServiceServer).GetUserByStuNumber(ctx, req.(*UserRequest))
}
return interceptor(ctx, in, info, handler)
}
// UserService_ServiceDesc is the grpc.ServiceDesc for UserService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var UserService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "user.UserService",
HandlerType: (*UserServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "GetUserByStuNumber",
Handler: _UserService_GetUserByStuNumber_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "user/user.proto",
}