first commit

This commit is contained in:
wuchao 2024-06-17 14:18:39 +08:00
commit 79c642932c
73 changed files with 6159 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,7 @@
package common
const (
TOKEN_PRE = "player_token_"
TOKEN_Admin = "Admin_token_"
ADMIN_V1 = "/admin/api/v1"
)

View File

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

View File

@ -0,0 +1,52 @@
package errorcode
const (
//成功
Success = 200
//参数错误
ParamError = 400
//未经授权
NotAuth = 401
//请求被禁止
Forbidden = 403
//找不到页面
NotFound = 404
//系统错误
SystemError = 500
//未登录
NotLogin = 1000
)
var MsgEN = map[int]string{
Success: "success",
ParamError: "param error",
NotAuth: "not authorized",
Forbidden: "forbidden",
NotFound: "not found",
SystemError: "system error",
}
var MsgZH = map[int]string{
Success: "请求成功",
ParamError: "参数错误",
NotFound: "数据不存在",
NotAuth: "未经授权",
NotLogin: "未登录",
}
var MsgMap map[string]map[int]string = map[string]map[int]string{"en": MsgZH}
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

View File

@ -0,0 +1,177 @@
package controllers
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"errors"
"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{}) (msgs []string, err error) {
if c.Request.Method == "GET" || c.Request.Method == "DELETE" {
err = c.ShouldBindQuery(request)
} else {
err = c.ShouldBindJSON(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 {
for _, v := range errValidate.(validator.ValidationErrors) {
msgs = append(msgs, v.Translate(trans))
}
err = errors.New(errorcode.GetMsg(errorcode.ParamError, ""))
return
}
}
return
}
// 重复读取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 GetRequest(c *gin.Context) interface{} {
request, _ := c.Get("request")
return request
}
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 utils.IsNil(data) {
data = struct{}{}
}
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

View File

@ -0,0 +1,12 @@
package domains
type Filter struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
}
type ProductFilter struct {
Page int `json:"page" form:"page"`
PageSize int `json:"page_size" form:"page_size"`
ProductName string `json:"product_name" form:"product_name"`
}

View File

@ -0,0 +1,23 @@
package domains
import "time"
type Product struct {
Id int
ProductName string
ProductType int
PacketRule int
ProductKind int
Amount string
CouponType int
IsSuperposition int
TemplateId int
Status int
IsNeedBill int
CreateTime time.Time
SupplierProductId int64
SupplierProductName string
Creator string
UpdateTime time.Time
}

View File

@ -0,0 +1,15 @@
package entities
type IdRequest struct {
Id int64 `json:"id"`
}
type PageRequest struct {
Page int64 `json:"page"`
PageSize int64 `json:"pageSize"`
}
type PageRsp struct {
Total int64 `json:"total"`
Data []interface{} `json:"data"`
}

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,105 @@
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/http/requestmapping"
"qteam/app/utils"
"strings"
)
func Auth() gin.HandlerFunc {
return func(c *gin.Context) {
c.ClientIP()
var tokens = strings.SplitN(c.GetHeader("Authorization"), " ", 2)
if len(tokens) != 2 || tokens[0] != "Bearer" {
controllers.HandCodeRes(c, nil, errorcode.NotLogin)
c.Abort()
return
}
// 验证token
token, claims, err := utils.ParseToken(tokens[1])
if err != nil || !token.Valid {
controllers.HandCodeRes(c, nil, errorcode.NotAuth)
c.Abort()
return
}
if err == nil {
c.Set("userId", claims.Id)
c.Set("phone", claims.Phone)
c.Next()
return
} else {
controllers.HandCodeRes(c, nil, errorcode.NotAuth)
c.Abort()
}
}
}
func Cors() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, platform,Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control,token, X-Requested-With")
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
}
}
func AdminAuth() 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_Admin+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 ValidateRequest() gin.HandlerFunc {
return func(c *gin.Context) {
var path = c.FullPath()
var handler func() interface{}
if strings.Index(path, "admin") >= 0 {
handler = requestmapping.BackendRequestMap[path]
} else {
handler = requestmapping.FrontRequestMap[path]
}
if handler == nil {
utils.Log(c, "path", path)
controllers.HandCodeRes(c, nil, errorcode.NotFound)
} else {
v := handler()
msg, err := controllers.GenRequest(c, v)
if err != nil {
utils.Log(c, "path", path)
controllers.Error(c, errorcode.ParamError, msg...)
} else {
c.Set("request", v)
c.Next()
}
}
}
}

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,31 @@
package middlewares
import (
"github.com/gin-gonic/gin"
"qteam/app/http/trace"
"qteam/app/utils"
"strconv"
)
const (
componentIDGOHttpServer = 5004
)
func Trace() gin.HandlerFunc {
return func(c *gin.Context) {
tracer, err := trace.Tracer()
if err != nil {
utils.Log(c, "trace err", err)
}
span := tracer.StartSpan("base trace")
// 可以自定义tag
span.SetName(c.Request.RequestURI)
span.Tag("methd", c.Request.Method)
c.Request = c.Request.WithContext(c)
c.Next()
code := c.Writer.Status()
span.Tag("status", strconv.Itoa(code))
span.Finish()
}
}

View File

@ -0,0 +1,8 @@
package requestmapping
var BackendRequestMap = map[string]func() interface{}{
//common.ADMIN_V1 + "/product/create": func() interface{} {
// return new(backend.ProductCreateRequest)
//},
}

View File

@ -0,0 +1,7 @@
package requestmapping
var FrontRequestMap = map[string]func() interface{}{
//"/v1/login": func() interface{} {
// return new(front.LoginRequest)
//},
}

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

@ -0,0 +1,30 @@
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())
}
}
//v1 := router.Group("/admin/api/v1")
//{
//
//}
}

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

@ -0,0 +1,59 @@
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.Use(middlewares.Cors())
router.NoRoute(controllers.Error404)
//api版本
//v1 := router.Group("/v1", middlewares.ValidateRequest())
//{
//
//}
router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
//router.GET("/hello", controllers.HelloHandler)
//router.GET("/create", controllers.HelloCreateHandler)
//router.GET("/update", controllers.UpdateHandler)
//router.GET("/delete", controllers.DeleteHandler)
//router.GET("/query", controllers.QueryHandler)
}

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/models/.gitkeep Normal file
View File

21
app/mq/hanlers.go Normal file
View File

@ -0,0 +1,21 @@
package mq
// 单聊
func SingleTalk(tag uint64, ch interface{}, msg []byte) {
//var data entities.SingTalkReq
//err := json.Unmarshal(msg, &data)
//utils.Log(nil, "msg", data)
//if err == nil {
// conn := netool.GetConnManagger().GetConnection(data.Msg.To)
// var sendOk = false
// if conn != nil && conn.IsActive() {
// if utils.WriteMsg(msgid.SINGLE_MSG, data, conn) {
// sendOk = true
// }
// }
// if !sendOk {
// common.PikaTool.Zadd(utils.GetRealKey(common2.USER_MSG)+data.Msg.To, data, time.Now().Unix())
// }
//}
}

27
app/mq/quenue.go Normal file
View File

@ -0,0 +1,27 @@
package mq
import (
"qteam/app/utils/mq"
)
func startQunue(name string, method interface{}, mqTp string, tp int, exhange string) {
if tp == 1 {
go mq.MqManager.GetMqByName(mqTp).Consume(name, method)
} else {
go mq.MqManager.GetMqByName(mqTp).DelyConsume(name, method)
}
}
// 队列服务
func StartQunueServer() error {
StartServer()
select {}
return nil
}
// 开启队列
func StartServer() error {
//startWorkers()
return nil
}

0
app/services/.gitkeep Normal file
View File

View File

@ -0,0 +1,90 @@
package market
import (
"bytes"
"encoding/json"
"github.com/pkg/errors"
"io/ioutil"
"net/http"
"qteam/config"
)
type MarketClient struct {
cfg config.MarketConfig
}
type MarketSendRequest struct {
AppId string `json:"app_id"` //APP ID
Sign string `json:"sign"` //签名
ReqCode string `json:"req_code"` //固定值voucher.create
MemId string `json:"mem_id"` //商户号
ReqSerialNo string `json:"req_serial_no"` //请求唯一流水号 最大32位
TimeTamp string `json:"timestamp"` //时间戳 yyyyMMddHHmmss
PosId string `json:"pos_id"` //商户方平台号
VoucherId string `json:"voucher_id"` //制码批次号
VoucherNum int `json:"voucher_num"` //请券数量,默认是 1
MobileNo string `json:"mobile_no"` //11 手机号,可传空字符串
SendMsg string `json:"send_msg"` //是否发送短信2- 发送 1-不发送
}
type MarketSenResponse struct {
VoucherId string `json:"voucher_id"` //制码批次号
VoucherCode string `json:"voucher_code"` //券码
ShortUrl string `json:"short_url"` //含二维码、条码的短链接
VoucherSdate string `json:"voucher_sdate"` //有效期起
VoucherEdate string `json:"voucher_edate"` //有效期止
CodeType string `json:"code_type"` //码类型: 00- 代金券 01- 满减券
}
type MarketResponse struct {
ErrCode string `json:"errCode"` //00-成功 其他:失败
Msg string `json:"msg"` //描 述 (失败时必填)
Data MarketSenResponse `json:"data"`
}
func (this *MarketSendRequest) toMap() (resultMap map[string]interface{}) {
// Marshal the struct to JSON, ignoring omitempty fields.
jsonBytes, err := json.Marshal(this)
if err != nil {
return
}
// Unmarshal the JSON into a map to get the final result.
err = json.Unmarshal(jsonBytes, &resultMap)
if err != nil {
return
}
return resultMap
}
func (this *MarketClient) doPost(url string, jsonBytes []byte) (body []byte, err error) {
// 创建POST请求
url = this.cfg.Host + url
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonBytes))
if err != nil {
return
}
// 设置Content-Type头
req.Header.Set("Content-Type", "application/json")
// 创建HTTP客户端
client := &http.Client{}
// 发送请求并处理响应
resp, err := client.Do(req)
if err != nil {
return
}
defer resp.Body.Close()
// 读取响应体
if resp.StatusCode != http.StatusOK {
err = errors.New("HTTP request failed: " + resp.Status)
return
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
return
}
return
}

View File

@ -0,0 +1,62 @@
package market
import (
"encoding/json"
"qteam/app/utils/encrypt"
"qteam/config"
"time"
)
func NewMarketClient(cfg config.MarketConfig) *MarketClient {
cfg.Sign = "-----BEGIN RSA PRIVATE KEY-----\n" + cfg.Sign + "\n-----END RSA PRIVATE KEY-----"
return &MarketClient{
cfg: cfg,
}
}
/*
MarketSend
券码生成接口
- 请求地址/openApi/v1/market/key/send
- 说明发券接口应支持使用同一流水号进行重复请求当调用该接口失败时 以使用同一流水号进行再次请求接口需要根据请求的流水号进行判断若无该流水 号的券码信息则新生成后返回若有该流水号的券码信息则直接返回该券码的信息
orderNo: 订单号
VoucherId: 制码批次号
MobileNo: 11 手机号可传空字符串
SendMsg: 是否发送短信2- 发送 1-不发送
*/
func (this *MarketClient) MarketSend(orderNo, VoucherId, MobileNo, SendMsg string) (res MarketResponse, err error) {
url := "/openApi/v1/market/key/send"
request := MarketSendRequest{
AppId: this.cfg.AppId,
ReqCode: this.cfg.ReqCode,
MemId: this.cfg.MemId,
PosId: this.cfg.PosId,
TimeTamp: time.Now().Format("20060102150405"),
VoucherId: VoucherId,
ReqSerialNo: orderNo,
VoucherNum: 1,
MobileNo: MobileNo,
SendMsg: SendMsg,
}
request.Sign, err = MakeRsaSign(this.cfg.Sign, request.toMap())
if err != nil {
return res, err
}
bytes, err := json.Marshal(request)
if err != nil {
return res, err
}
data, err := this.doPost(url, bytes)
if err != nil {
return res, err
}
err = json.Unmarshal(data, &res)
// 加密
if len(res.Data.ShortUrl) > 0 {
res.Data.ShortUrl = encrypt.AesEncryptCBC([]byte(res.Data.ShortUrl), []byte(this.cfg.SecretKey))
}
return res, err
}

View File

@ -0,0 +1,34 @@
package market
import (
"fmt"
"github.com/qit-team/snow-core/kernel/server"
"os"
"qteam/app/utils"
"qteam/config"
"testing"
)
func TestMarketSendRequest_Market(t *testing.T) {
opts := config.GetOptions()
if opts.ShowVersion {
fmt.Printf("%s\ncommit %s\nbuilt on %s\n", server.Version, server.BuildCommit, server.BuildDate)
os.Exit(0)
}
//加载配置
conf, err := config.Load(opts.ConfFile)
if err != nil {
utils.Log(nil, "err", err.Error())
return
}
client := NewMarketClient(conf.OpenApiMarketConfig)
//data, err := client.MarketSend("123456789111", "1717567048171", "", "2")
data, err := client.MarketSend("123111", "1717", "", "2")
if err != nil {
t.Error(err)
}
t.Log(data)
}

137
app/third/market/rsa.go Normal file
View File

@ -0,0 +1,137 @@
package market
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"errors"
"fmt"
"sort"
)
// getSignString 使用 xx=aa&yy=bb 的字符串拼接
func getSignString(data map[string]interface{}) string {
keys := make([]string, 0, len(data))
for key := range data {
keys = append(keys, key)
}
sort.Strings(keys)
signString := ""
separator := ""
for _, key := range keys {
value := data[key]
if key == "sign" || value == nil {
continue
}
signString += fmt.Sprintf("%s%s=%v", separator, key, value)
separator = "&"
}
return signString
}
// VerifyRsaSign 签名验证
func VerifyRsaSign(publicKey string, data map[string]interface{}) (map[string]interface{}, error) {
// 对 sign nonce timestamp appId 升序排序
// 使用 xx=aa&yy=bb 的字符串拼接
// 商户的公钥验签 RSA2验签
signString := getSignString(data)
rsaPubKey, err := parseRSAPublicKeyFromPEM([]byte(publicKey))
if err != nil {
return nil, err
}
signature, err := base64.StdEncoding.DecodeString(data["sign"].(string))
if err != nil {
return nil, err
}
hashed := sha256.Sum256([]byte(signString))
err = rsa.VerifyPKCS1v15(rsaPubKey, crypto.SHA256, hashed[:], signature)
if err != nil {
return nil, errors.New("签名验证失败")
}
return data, nil
}
// MakeRsaSign 生成签名
func MakeRsaSign(privateKey string, data map[string]interface{}) (string, error) {
// 对 sign nonce timestamp appId 升序排序
// 使用 xx=aa&yy=bb 的字符串拼接
// 营销系统生成的私钥生成签名 RSA2加签
signString := getSignString(data)
privKey, err := parseRSAPrivateKeyFromPEM([]byte(privateKey))
if err != nil {
return "", errors.New("私钥解析失败")
}
hashed := sha256.Sum256([]byte(signString))
signature, err := rsa.SignPKCS1v15(rand.Reader, privKey, crypto.SHA256, hashed[:])
if err != nil {
return "", errors.New("签名失败")
}
return base64.StdEncoding.EncodeToString(signature), nil
}
// ParseRSAPrivateKeyFromPEM 解析私钥
func parseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error) {
var err error
// Parse PEM block
var block *pem.Block
if block, _ = pem.Decode(key); block == nil {
return nil, errors.New("私钥解析失败: 无效的PEM格式")
}
var parsedKey interface{}
if parsedKey, err = x509.ParsePKCS1PrivateKey(block.Bytes); err != nil {
if parsedKey, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil {
return nil, err
}
}
var pkey *rsa.PrivateKey
var ok bool
if pkey, ok = parsedKey.(*rsa.PrivateKey); !ok {
return nil, errors.New("密钥不是有效的RSA私钥")
}
return pkey, nil
}
// parseRSAPublicKeyFromPEM parses a PEM encoded PKCS1 or PKCS8 public key
func parseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error) {
var err error
// Parse PEM block
var block *pem.Block
if block, _ = pem.Decode(key); block == nil {
return nil, errors.New("公钥解析失败: 无效的PEM格式")
}
// Parse the key
var parsedKey interface{}
if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil {
if cert, err := x509.ParseCertificate(block.Bytes); err == nil {
parsedKey = cert.PublicKey
} else {
return nil, err
}
}
var pkey *rsa.PublicKey
var ok bool
if pkey, ok = parsedKey.(*rsa.PublicKey); !ok {
return nil, errors.New("密钥不是有效的RSA公钥")
}
return pkey, nil
}

View File

@ -0,0 +1,164 @@
package openapiService
import (
"context"
"crypto/aes"
"encoding/base64"
"gitee.com/chengdu_blue_brothers/openapi-go-sdk/api"
"gitee.com/chengdu_blue_brothers/openapi-go-sdk/notify"
"net/http"
"qteam/app/models/orderdetailsmodel"
"qteam/app/models/ordersmodel"
"qteam/app/models/usercouponmodel"
"qteam/app/utils"
"qteam/config"
"time"
)
func CreatClient() (client *api.Client, err error) {
merchantId := config.GetConf().OpenApi.MerchantId
secretKey := config.GetConf().OpenApi.SecretKey
isProd := config.GetConf().OpenApi.IsProd
timeout := 10 * time.Second // 请求超时时间
client, err = api.NewClient(merchantId, secretKey, isProd, timeout)
if err != nil {
return nil, err
}
return client, nil
}
type RechargeOrderReq struct {
OutTradeNo string
ProductId int
RechargeAccount string
AccountType int
Number int
}
func RechargeOrder(rechargeOrderReq RechargeOrderReq) (result *api.RechargeOrderResp, err error) {
req := &api.RechargeOrderReq{
OutTradeNo: rechargeOrderReq.OutTradeNo,
ProductId: rechargeOrderReq.ProductId,
RechargeAccount: rechargeOrderReq.RechargeAccount,
AccountType: 0,
Number: 1,
NotifyUrl: config.GetConf().OpenApi.NotifyUrl,
}
client, err := CreatClient()
if err != nil {
return nil, err
}
result, err = client.RechargeOrder(context.Background(), req)
if err != nil {
return nil, err
}
if result.Code != api.CodeCreateOrderSuccess {
return
}
return
}
func ReCardOrder(CardOrderReq api.CardOrderReq) (result *api.CardOrderResp, err error) {
req := &api.CardOrderReq{
OutTradeNo: CardOrderReq.OutTradeNo,
ProductId: CardOrderReq.ProductId,
AccountType: 0,
Number: 1,
NotifyUrl: config.GetConf().OpenApi.NotifyUrl,
}
client, err := CreatClient()
if err != nil {
return nil, err
}
result, err = client.CardOrder(context.Background(), req)
if err != nil {
return nil, err
}
if result.Code != api.CodeCreateOrderSuccess {
return
}
return
}
func DecryptCard(encCode string) (decode string, err error) {
defer func() error {
if r := recover(); r != nil {
utils.Log(nil, "解密错误", err)
}
return err
}()
client, err := CreatClient()
if err != nil {
return
}
decryptedCode, err := decryptAES(encCode, client.GetSecretKey())
if err != nil {
return
}
return decryptedCode, nil
}
// AES 解密
func decryptAES(encryptedData string, secretKey string) (string, error) {
// 第一步对加密的卡密做base64 decode
encryptedBytes, err := base64.StdEncoding.DecodeString(encryptedData)
if err != nil {
return "", err
}
// 第二步使用aes-256-ecb解密
cipher, _ := aes.NewCipher([]byte(secretKey))
decrypted := make([]byte, len(encryptedBytes))
size := 16
for bs, be := 0, size; bs < len(encryptedBytes); bs, be = bs+size, be+size {
cipher.Decrypt(decrypted[bs:be], encryptedBytes[bs:be])
}
paddingSize := int(decrypted[len(decrypted)-1])
return string(decrypted[0 : len(decrypted)-paddingSize]), nil
}
func ParseAndVerify(request *http.Request) (req *notify.OrderReq, err error) {
// 第一步初使化client实例
merchantId := config.GetConf().OpenApi.MerchantId
secretKey := config.GetConf().OpenApi.SecretKey
client := notify.NewNotify(merchantId, secretKey)
// 第二步:验签并解析结果
req, err = client.ParseAndVerify(request)
if err != nil {
return nil, err
}
return
}
// NotifyOperation /**
func NotifyOperation(order ordersmodel.Orders, req *notify.OrderReq) (err error) {
var session = ordersmodel.GetInstance().GetDb().NewSession()
session.Begin()
updateOrder := ordersmodel.Orders{Status: 3}
_, err = session.Where("Id = ?", order.Id).Update(&updateOrder)
if err != nil {
session.Rollback()
return
}
//卡密
if req.CardCode != "" {
//订单详情
updateOrderDetail := orderdetailsmodel.OrderDetails{Url: req.CardCode.Value()}
_, err = session.Where("OrderId = ?", order.Id).Update(&updateOrderDetail)
if err != nil {
session.Rollback()
return
}
userCouponDetail := usercouponmodel.UserCoupon{OrderInfo: req.CardCode.Value()}
_, err = session.Where("OrderId = ?", order.Id).Update(&userCouponDetail)
if err != nil {
session.Rollback()
return
}
}
session.Commit()
return
}

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,37 @@
package encrypt
import (
"math/rand"
"unsafe"
)
const lettersString = "0123456789abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"
// 字符串长度
const number = 16
/*
16位码前15位随机字符串最后一位通过前15位字符串计算校验生成
*/
func LotteryEncryptEncode() string {
b := make([]byte, number)
var sum byte
for i := 0; i < number-1; i++ {
b[i] = lettersString[rand.Int63()%int64(len(lettersString))]
sum += b[i]
}
b[number-1] = lettersString[sum%byte(len(lettersString))]
return *(*string)(unsafe.Pointer(&b))
}
func LotteryEncryptDecode(str string) bool {
var sum byte
for i := 0; i < len(str)-1; i++ {
sum += str[i]
}
if lettersString[sum%byte(len(lettersString))] != str[len(str)-1] {
return false
}
return true
}

View File

@ -0,0 +1,15 @@
package encrypt
import "testing"
func TestLotteryEncryptEncode(t *testing.T) {
code := LotteryEncryptEncode()
t.Log(code)
}
func TestLotteryEncryptDecode(t *testing.T) {
code := LotteryEncryptEncode()
t.Log(code)
result := LotteryEncryptDecode(code)
t.Log(result)
}

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{}),
)
}

36
app/utils/mq/mqmanager.go Normal file
View File

@ -0,0 +1,36 @@
package mq
import (
common3 "qteam/app/constants/common"
mq "qteam/app/utils/mq/mqs"
"sync"
)
var (
MqManager = CMqManager{}
once sync.Once
)
type Imq interface {
Produce(name string, log interface{}, delayTime int, args ...interface{}) error
Consume(name string, hand interface{})
DelyConsume(name string, hand interface{})
}
type CMqManager struct {
mqs map[string]Imq
}
func (this *CMqManager) InitMq() {
this.mqs = make(map[string]Imq)
//this.mqs[common.MQ_RABBIT] = RabbitMq{}
//this.mqs[common.MQ_NSQ] = NsqMq{}
this.mqs[common3.MQ_NATS] = mq.NatsMq{}
this.mqs[common3.MQ_KFK] = mq.KafkaMq{}
}
func (this *CMqManager) GetMqByName(name string) Imq {
once.Do(func() {
this.InitMq()
})
return this.mqs[name]
}

89
app/utils/mq/mqs/kafka.go Normal file
View File

@ -0,0 +1,89 @@
package mq
import (
"context"
"encoding/json"
"fmt"
"github.com/Shopify/sarama"
"github.com/qit-team/snow-core/redis"
"qteam/app/utils"
"qteam/config"
"strconv"
"sync"
)
type KafkaMq struct {
}
// 同步
func (n KafkaMq) Produce(name string, log interface{}, delayTime int, args ...interface{}) error {
kafConfig := sarama.NewConfig()
kafConfig.Producer.RequiredAcks = sarama.WaitForAll // 发送完数据需要leader和follow都确认
kafConfig.Producer.Partitioner = sarama.NewRandomPartitioner // 新选出一个partition
kafConfig.Producer.Return.Successes = true // 成功交付的消息将在success channel返回
// 构造一个消息
msg := &sarama.ProducerMessage{}
msg.Topic = name
var data, _ = json.Marshal(log)
msg.Value = sarama.StringEncoder(string(data))
// 连接kafka
client, err := sarama.NewSyncProducer(config.GetConf().KafkaUrl, kafConfig)
if err != nil {
fmt.Println("producer closed, err:", err)
return nil
}
defer client.Close()
// 发送消息
pid, offset, err := client.SendMessage(msg)
if err != nil {
utils.Log(nil, "send msg failed, err:", err, pid, offset)
return nil
}
return nil
}
func (n KafkaMq) Consume(name string, hand interface{}) {
consumer, err := sarama.NewConsumer(config.GetConf().KafkaUrl, nil)
if err != nil {
utils.Log(nil, "kafka comsume", err.Error())
return
}
partitionList, err := consumer.Partitions(name) // 根据topic取到所有的分区
if err != nil {
utils.Log(nil, "kafka comsume", err.Error())
return
}
//utils.Log(nil,"kafka comsume",name,partitionList)
for partition := range partitionList { // 遍历所有的分区
// 针对每个分区创建一个对应的分区消费者
var offsetReDis, _ = redis.GetRedis().Incr(context.Background(), "kafka_consume:"+strconv.Itoa(int(partition))).Result() //保证多消费者不重复消费
var offset int64 = sarama.OffsetNewest
if offsetReDis > 0 {
//offset = int64(offsetReDis)
}
pc, err := consumer.ConsumePartition(name, int32(partition), offset)
//utils.Log(nil,"partion",int32(partition))
if err != nil {
fmt.Printf("failed to start consumer for partition %d,err:%v\n", partition, err)
return
}
defer pc.AsyncClose()
var wg sync.WaitGroup
wg.Add(1)
// 异步从每个分区消费信息
go func(sarama.PartitionConsumer) {
for msg := range pc.Messages() {
defer wg.Done()
var handler = hand.(func(tag uint64, ch interface{}, msg []byte))
handler(0, nil, msg.Value)
//utils.Log(nil,"hand msg",string(msg.Value),msg.Offset)
}
}(pc)
wg.Wait()
}
}
func (n KafkaMq) DelyConsume(name string, hand interface{}) {
}

48
app/utils/mq/mqs/nats.go Normal file
View File

@ -0,0 +1,48 @@
package mq
import (
"encoding/json"
"fmt"
"github.com/nats-io/nats.go"
_ "github.com/nats-io/nats.go"
"github.com/streadway/amqp"
"qteam/app/utils"
"qteam/config"
)
type NatsMq struct {
Address string
nc *nats.Conn
}
func (n NatsMq) Produce(name string, log interface{}, delayTime int, args ...interface{}) error {
name = config.GetConf().ServiceName + "_" + name
fmt.Println("nats produce", name)
nc, _ := nats.Connect(n.Address)
defer nc.Close()
var content, err = json.Marshal(log)
nc.Publish(name, content)
return err
}
func (n NatsMq) Consume(name string, hand interface{}) {
if n.nc == nil || n.nc.IsClosed() == true {
if n.nc != nil {
n.nc.Close()
}
n.nc, _ = nats.Connect(n.Address)
}
nc := n.nc
fmt.Println("nats comsume", name)
//defer nc.Close()
_, err := nc.Subscribe(name, func(m *nats.Msg) {
utils.Log(nil, "Received a message: %s", string(m.Data))
var handler = hand.(func(tag uint64, ch *amqp.Channel, msg []byte))
handler(0, nil, m.Data)
})
fmt.Println("ttt", err)
}
func (n NatsMq) DelyConsume(name string, hand interface{}) {
}

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
}

160
app/utils/sm2/sm2.go Normal file
View File

@ -0,0 +1,160 @@
package sm2
import (
"crypto/rand"
"encoding/base64"
"encoding/hex"
"fmt"
"github.com/tjfoc/gmsm/sm2"
"math/big"
"qteam/config"
"strings"
)
// 生成公钥、私钥
func GenerateSM2Key() (PublicKey string, PrivateKey string, err error) {
// 生成私钥、公钥
privKey, err := sm2.GenerateKey(rand.Reader)
if err != nil {
fmt.Println("生成密钥对失败:", err)
return "", "", err
}
return PublicKeyToString(&privKey.PublicKey), PrivateKeyToString(privKey), nil
}
// PublicKeyToString 公钥sm2.PublicKey转字符串(与java中org.bouncycastle.crypto生成的公私钥完全互通使用)
func PublicKeyToString(publicKey *sm2.PublicKey) string {
xBytes := publicKey.X.Bytes()
yBytes := publicKey.Y.Bytes()
// 确保坐标字节切片长度相同
byteLen := len(xBytes)
if len(yBytes) > byteLen {
byteLen = len(yBytes)
}
// 为坐标补齐前导零
xBytes = append(make([]byte, byteLen-len(xBytes)), xBytes...)
yBytes = append(make([]byte, byteLen-len(yBytes)), yBytes...)
// 添加 "04" 前缀
publicKeyBytes := append([]byte{0x04}, append(xBytes, yBytes...)...)
return strings.ToUpper(hex.EncodeToString(publicKeyBytes))
}
// PrivateKeyToString 私钥sm2.PrivateKey 转字符串(与java中org.bouncycastle.crypto生成的公私钥完全互通使用)
func PrivateKeyToString(privateKey *sm2.PrivateKey) string {
return strings.ToUpper(hex.EncodeToString(privateKey.D.Bytes()))
}
func SM2Decrypt(cipherText string) (string, error) {
if cipherText == "" {
return "", nil
}
decodedBytes, err := base64.StdEncoding.DecodeString(cipherText)
if err != nil {
fmt.Println("解码错误:", err)
return "", nil
}
decrypt, err := decryptLoc(config.GetConf().Sm2.PublicKey, config.GetConf().Sm2.PrivateKey, string(decodedBytes))
if err != nil {
return "", err
}
return decrypt, nil
}
func SM2Encrypt(cipherText string) (string, error) {
if cipherText == "" {
return "", nil
}
decrypt, err := encryptLoc(config.GetConf().Sm2.PublicKey, cipherText)
if err != nil {
return "", err
}
return decrypt, nil
}
func encryptLoc(publicKeyStr, data string) (string, error) {
publicKeyObj, err := StringToPublicKey(publicKeyStr)
if err != nil {
fmt.Println(err)
}
decrypt, err := sm2.Encrypt(publicKeyObj, []byte(data), rand.Reader, sm2.C1C2C3)
if err != nil {
fmt.Println(err)
}
resultStr := hex.EncodeToString(decrypt)
return base64.StdEncoding.EncodeToString([]byte(resultStr)), nil
}
func decryptLoc(publicKeyStr, privateKeyStr, cipherText string) (string, error) {
publicKeyObj, err := StringToPublicKey(publicKeyStr)
if err != nil {
fmt.Println(err)
}
privateKeyObj, err := StringToPrivateKey(privateKeyStr, publicKeyObj)
if err != nil {
fmt.Println(err)
}
decodeString, err := hex.DecodeString(cipherText)
decrypt, err := sm2.Decrypt(privateKeyObj, decodeString, sm2.C1C2C3)
if err != nil {
fmt.Println(err)
}
resultStr := string(decrypt)
fmt.Println("解密后的字符串:", resultStr)
return resultStr, nil
}
// StringToPrivateKey 私钥还原为 sm2.PrivateKey对象(与java中org.bouncycastle.crypto生成的公私钥完全互通使用)
func StringToPrivateKey(privateKeyStr string, publicKey *sm2.PublicKey) (*sm2.PrivateKey, error) {
privateKeyBytes, err := hex.DecodeString(privateKeyStr)
if err != nil {
return nil, err
}
// 将字节切片转换为大整数
d := new(big.Int).SetBytes(privateKeyBytes)
// 创建 sm2.PrivateKey 对象
privateKey := &sm2.PrivateKey{
PublicKey: *publicKey,
D: d,
}
return privateKey, nil
}
// StringToPublicKey 公钥字符串还原为 sm2.PublicKey 对象(与java中org.bouncycastle.crypto生成的公私钥完全互通使用)
func StringToPublicKey(publicKeyStr string) (*sm2.PublicKey, error) {
publicKeyBytes, err := hex.DecodeString(publicKeyStr)
if err != nil {
return nil, err
}
// 提取 x 和 y 坐标字节切片
curve := sm2.P256Sm2().Params()
byteLen := (curve.BitSize + 7) / 8
xBytes := publicKeyBytes[1 : byteLen+1]
yBytes := publicKeyBytes[byteLen+1 : 2*byteLen+1]
// 将字节切片转换为大整数
x := new(big.Int).SetBytes(xBytes)
y := new(big.Int).SetBytes(yBytes)
// 创建 sm2.PublicKey 对象
publicKey := &sm2.PublicKey{
Curve: curve,
X: x,
Y: y,
}
return publicKey, nil
}
// 验证签名
func VerSm2Sig(pub *sm2.PublicKey, msg []byte, sign []byte) bool {
isok := pub.Verify(msg, sign)
return isok
}

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

@ -0,0 +1,412 @@
package utils
import (
"context"
"crypto/md5"
"crypto/rand"
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
"fmt"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v4"
"github.com/pkg/errors"
"github.com/qit-team/snow-core/redis"
"github.com/tjfoc/gmsm/sm2"
"github.com/tjfoc/gmsm/x509"
mrand "math/rand"
"net"
"os"
"path/filepath"
"qteam/app/constants/common"
"qteam/config"
"reflect"
"regexp"
"runtime"
"strings"
"time"
"unicode"
)
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 int) string {
//去生成一个token返回给客户端
m5 := SToMd5(playerName + time.Now().String())
var pid = int64(playerId)
bsCode := Encode(pid)
tk := m5 + bsCode
//将token放入redis
_, err := redis.GetRedis(redis.SingletonMain).SetEX(context.Background(), GetRealKey(common.TOKEN_PRE+tk), playerId, time.Duration(3600)*time.Second).Result()
if err != nil {
Log(nil, "token", err)
}
return tk
}
// ExistFile 检查给定的文件是否存在
func ExistFile(dirPath string) (exist bool, err error) {
// 使用filepath.Abs获取绝对路径确保我们处理的是实际存在的路径
absPath, err := filepath.Abs(dirPath)
if err != nil {
return exist, fmt.Errorf("failed to get absolute path: %v", err)
}
// 使用os.Stat检查路径是否存在
_, err = os.Stat(absPath)
if errors.Is(err, os.ErrNotExist) {
return exist, nil
}
return true, err
}
// StrToTimeShanghai 字符串转时间
func StrToTimeShanghai(strTime string) (t time.Time, err error) {
location, _ := time.LoadLocation("Asia/Shanghai")
return time.ParseInLocation(time.DateTime, strTime, location)
}
func GenIncrementId(tableName string) (int, error) {
var id, err = redis.GetRedis().Incr(context.Background(), GetRealKey(tableName)).Result()
return int(id), err
}
// CheckOrCreateYmdDirectory 创建ymd目录
func CheckOrCreateYmdDirectory(dirPath string) (SavePath string, err error) {
// 使用filepath.Abs获取绝对路径确保我们处理的是实际存在的路径
now := time.Now()
year := now.Year()
month := now.Month()
day := now.Day()
savePath := filepath.Join("uploads", dirPath, fmt.Sprintf("%d", year), fmt.Sprintf("%d", month), fmt.Sprintf("%d", day))
absPath, err := filepath.Abs(savePath)
if err != nil {
return SavePath, fmt.Errorf("failed to get absolute path: %v", err)
}
// 使用os.Stat检查路径是否存在
info, err := os.Stat(absPath)
if err != nil {
if !os.IsNotExist(err) {
return SavePath, fmt.Errorf("error checking directory: %v", err)
}
// 目录不存在,尝试创建
err = os.MkdirAll(absPath, 0755) // 0755是默认权限可以按需调整
if err != nil {
return SavePath, fmt.Errorf("failed to create directory: %v", err)
}
} else if !info.IsDir() {
return SavePath, fmt.Errorf("%s exists but it's not a directory", absPath)
}
SavePath = absPath
return SavePath, nil
}
// CheckOrCreateDirectory 检查给定的目录是否存在,如果不存在则创建它
func CheckOrCreateDirectory(dirPath string) error {
// 使用filepath.Abs获取绝对路径确保我们处理的是实际存在的路径
absPath, err := filepath.Abs(dirPath)
if err != nil {
return fmt.Errorf("failed to get absolute path: %v", err)
}
// 使用os.Stat检查路径是否存在
info, err := os.Stat(absPath)
if err != nil {
if !os.IsNotExist(err) {
return fmt.Errorf("error checking directory: %v", err)
}
// 目录不存在,尝试创建
err = os.MkdirAll(absPath, 0755) // 0755是默认权限可以按需调整
if err != nil {
return fmt.Errorf("failed to create directory: %v", err)
}
} else if !info.IsDir() {
return fmt.Errorf("%s exists but it's not a directory", absPath)
}
return nil
}
func ToSnakeCase(s string) string {
ch, en := splitChineseEnglish(s)
fmt.Println(ch, en)
re := regexp.MustCompile("([a-z0-9])([A-Z])")
snake := re.ReplaceAllString(en, "${1}_${2}")
return strings.ToLower(snake) + ch
}
func splitChineseEnglish(input string) (chinese string, english string) {
var index = findChineseStartIndex(input)
return input[index:], input[0:index]
}
func findChineseStartIndex(input string) int {
runes := []rune(input)
for i, r := range runes {
if unicode.Is(unicode.Han, r) {
return i
}
}
return -1 // 如果没有找到中文字符,返回-1
}
func DownloadFileFromOss(url, savePath string) error {
_, bucket, err := AliOssClient()
if err != nil {
return err
}
err = bucket.GetObjectToFileWithURL(url, savePath)
return err
}
func EntityCopy(dst, src interface{}) {
dstValue := reflect.ValueOf(dst).Elem()
srcValue := reflect.ValueOf(src).Elem()
for i := 0; i < srcValue.NumField(); i++ {
srcField := srcValue.Field(i)
srcName := srcValue.Type().Field(i).Name
dstFieldByName := dstValue.FieldByName(srcName)
if dstFieldByName.IsValid() {
switch dstFieldByName.Kind() {
case reflect.Ptr:
switch srcField.Kind() {
case reflect.Ptr:
if srcField.IsNil() {
dstFieldByName.Set(reflect.New(dstFieldByName.Type().Elem()))
} else {
dstFieldByName.Set(srcField)
}
default:
dstFieldByName.Set(srcField.Addr())
}
default:
switch srcField.Kind() {
case reflect.Ptr:
if srcField.IsNil() {
dstFieldByName.Set(reflect.Zero(dstFieldByName.Type()))
} else {
dstFieldByName.Set(srcField.Elem())
}
default:
if srcField.Type().Name() == "Time" {
if (srcField.Interface().(time.Time).Unix()) < 1 {
dstFieldByName.Set(reflect.ValueOf(""))
} else {
dstFieldByName.Set(reflect.ValueOf(srcField.Interface().(time.Time).Format("2006-01-02 15:04:05")))
}
} else {
dstFieldByName.Set(srcField)
}
}
}
}
}
}
// AliOssClient 返回Oss客户链接
func AliOssClient() (client *oss.Client, Bucket *oss.Bucket, err error) {
/*
oss 的相关配置信息
*/
bucketName := config.GetConf().AliOss.BucKet
endpoint := config.GetConf().AliOss.EndPoint
accessKeyId := config.GetConf().AliOss.AccessKey
accessKeySecret := config.GetConf().AliOss.AccessKeySecret
//domain := config.GetConf().AliOss.Domain
//Dir := config.GetConf().AliOss.Dir
//创建OSSClient实例
client, err = oss.New(endpoint, accessKeyId, accessKeySecret)
if err != nil {
return nil, nil, err
}
// 获取存储空间
Bucket, err = client.Bucket(bucketName)
if err != nil {
return nil, nil, err
}
return client, Bucket, nil
}
func GetSHA256HashCode(message []byte) string {
//方法一:
//创建一个基于SHA256算法的hash.Hash接口的对象
hash := sha256.New()
//输入数据
hash.Write(message)
//计算哈希值
bytes := hash.Sum(nil)
//将字符串编码为16进制格式,返回字符串
hashCode := hex.EncodeToString(bytes)
//返回哈希值
return hashCode
//方法二:
//bytes2:=sha256.Sum256(message)//计算哈希值返回一个长度为32的数组
//hashcode2:=hex.EncodeToString(bytes2[:])//将数组转换成切片转换成16进制返回字符串
//return hashcode2
}
func GetSHA512HashCode(message []byte) string {
hash := sha512.New()
hash.Write(message)
bytes := hash.Sum(nil)
hashCode := hex.EncodeToString(bytes)
return hashCode
}
// SM2Encode sm2公钥加密
func SM2Encode(pubKey string, plaintext string, mode int) (string, error) {
pubMen, err := x509.ReadPublicKeyFromHex(pubKey)
if err != nil {
return "", err
}
msg := []byte(plaintext)
ciphertxt, err := sm2.Encrypt(pubMen, msg, rand.Reader, mode)
if err != nil {
return "", err
}
return hex.EncodeToString(ciphertxt), nil
}
// SM2Decode sm2私钥解密
func SM2Decode(privKey string, data string, mode int) (string, error) {
priv, err := x509.ReadPrivateKeyFromHex(privKey)
if err != nil {
return "", err
}
ciphertext, err := hex.DecodeString(data)
if err != nil {
return "", err
}
plaintext, err := sm2.Decrypt(priv, []byte(ciphertext), mode)
if err != nil {
return "", err
}
return string(plaintext), nil
}
func GenerateOrderNumber() string {
// 生成当前日期部分例如20231008
datePart := time.Now().Format("20060102150405")
// 生成随机数部分4位随机数
mrand.Seed(time.Now().UnixNano())
randomPart := fmt.Sprintf("%04d", mrand.Intn(10000))
// 添加固定前缀
prefix := "SN"
// 最终的订单号由前缀、日期和随机数部分组成
orderNumber := fmt.Sprintf("%s%s%s", prefix, datePart, randomPart)
return orderNumber
}
func IsNil(x interface{}) bool {
if x == nil {
return true
}
rv := reflect.ValueOf(x)
return rv.Kind() == reflect.Ptr && rv.IsNil()
}
type User struct {
Id int
Phone string
}
func GeneratorJwtToken(user User) string {
// 定义一个用于签名的密钥
secretKey := []byte(config.GetConf().Jwt.SecretKey)
// 创建一个MapClaims对象用于存放自定义的声明信息
claims := jwt.MapClaims{
"id": user.Id,
"phone": user.Phone,
"exp": time.Now().Add(time.Hour * 24).Unix(), // 设置过期时间为24小时后
}
// 使用HS256算法创建一个Token对象
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// 使用密钥对Token进行签名生成JWT字符串
tokenString, err := token.SignedString(secretKey)
if err != nil {
fmt.Println("Failed to create token:", err)
return ""
}
return tokenString
}
type Claims struct {
Id int
Phone string
jwt.StandardClaims
}
func ParseToken(tokenString string) (*jwt.Token, *Claims, error) {
Claims := &Claims{}
token, err := jwt.ParseWithClaims(tokenString, Claims, func(token *jwt.Token) (i interface{}, err error) {
return []byte(config.GetConf().Jwt.SecretKey), nil
})
return token, Claims, err
}

79
bootstrap/bootstrap.go Normal file
View File

@ -0,0 +1,79 @@
package bootstrap
import (
"github.com/qit-team/snow-core/log/accesslogger"
"qteam/app/jobs"
"qteam/app/jobs/basejob"
"qteam/config"
"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/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

125
config/config.go Normal file
View File

@ -0,0 +1,125 @@
package config
import (
"github.com/BurntSushi/toml"
"github.com/qit-team/snow-core/config"
"os"
)
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"`
Sm2 Sm2 `toml:"Sm2"`
OpenApiMarketConfig MarketConfig `toml:"MarketConfig"`
OpenApi OpenApi `toml:"OpenApi"`
Jwt Jwt `toml:"Jwt"`
AliOss AliOss `toml:"AliOss"`
}
type AliOss struct {
AccessKey string
AccessKeySecret string
EndPoint string
BucKet string
Domain string
Dir string
}
type Jwt struct {
SecretKey string
}
type OpenApi struct {
MerchantId string
SecretKey string
IsProd bool
NotifyUrl string
TimeOut int
}
type MarketConfig struct {
AppId string `json:"app_id"` //APP ID
Sign string `json:"sign"` //签名
ReqCode string `json:"req_code"` //固定值voucher.create
MemId string `json:"mem_id"` //商户号
PosId string `json:"pos_id"` //商户方平台号
Host string `json:"-"`
SecretKey string
}
type Sm2 struct {
PublicKey string
PrivateKey string
}
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", "admin", "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{}) {
}

116
go.mod Normal file
View File

@ -0,0 +1,116 @@
module qteam
go 1.21
require (
gitee.com/chengdu_blue_brothers/openapi-go-sdk v0.0.2
github.com/BurntSushi/toml v0.4.1
github.com/Shopify/sarama v1.19.0
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
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/golang-jwt/jwt/v4 v4.5.0
github.com/nacos-group/nacos-sdk-go/v2 v2.2.5
github.com/nats-io/nats.go v1.9.1
github.com/openzipkin/zipkin-go v0.2.2
github.com/pkg/errors v0.9.1
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/streadway/amqp v0.0.0-20190827072141-edfb9018d271
github.com/swaggo/gin-swagger v1.3.3
github.com/swaggo/swag v1.7.9
github.com/tjfoc/gmsm v1.4.1
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
)
require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68 // indirect
github.com/alibabacloud-go/tea v1.1.17 // indirect
github.com/alibabacloud-go/tea-utils v1.4.4 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1800 // indirect
github.com/aliyun/alibabacloud-dkms-gcs-go-sdk v0.2.2 // indirect
github.com/aliyun/alibabacloud-dkms-transfer-go-sdk v0.1.7 // indirect
github.com/andybalholm/brotli v1.0.4 // indirect
github.com/apache/rocketmq-client-go/v2 v2.1.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/eapache/go-resiliency v1.1.0 // indirect
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 // indirect
github.com/eapache/queue v1.1.0 // indirect
github.com/emirpasic/gods v1.12.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-openapi/spec v0.20.4 // indirect
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-playground/validator/v10 v10.9.0 // indirect
github.com/go-redis/redis/v8 v8.11.4 // indirect
github.com/goccy/go-json v0.8.1 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/hetiansu5/accesslog v1.0.0 // indirect
github.com/hetiansu5/cores v1.0.0 // indirect
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.13.6 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible // indirect
github.com/lestrrat-go/strftime v1.0.5 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/nats-io/jwt v0.3.2 // indirect
github.com/nats-io/nkeys v0.1.3 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/pierrec/lz4 v2.0.5+incompatible // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a // indirect
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/tidwall/gjson v1.12.1 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/ugorji/go/codec v1.2.6 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.1 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.21.0 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.1.0 // indirect
golang.org/x/tools v0.6.0 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
gopkg.in/ini.v1 v1.66.2 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
stathat.com/c/consistent v1.0.0 // indirect
xorm.io/builder v0.3.9 // indirect
xorm.io/core v0.7.3 // indirect
xorm.io/xorm v1.2.5 // indirect
)

1388
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

128
main.go Normal file
View File

@ -0,0 +1,128 @@
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)
case "all":
go server.StartHttp(pidFile, conf.Api, routes.RegisterRoute)
go server.StartHttp(pidFile, conf.Admin, routes.RegisterAdminRoute)
select {}
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",
}