mirror of
https://github.com/bufanyun/hotgo.git
synced 2025-11-12 12:13:51 +08:00
v2.0
This commit is contained in:
24
server/internal/library/cache/cache.go
vendored
Normal file
24
server/internal/library/cache/cache.go
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
// Package cache
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2022 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
//
|
||||
package cache
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gcache"
|
||||
)
|
||||
|
||||
func New() *gcache.Cache {
|
||||
c := gcache.New()
|
||||
|
||||
//redis
|
||||
adapter := gcache.NewAdapterRedis(g.Redis())
|
||||
|
||||
//内存
|
||||
//adapter := gcache.NewAdapterMemory()
|
||||
c.SetAdapter(adapter)
|
||||
return c
|
||||
}
|
||||
44
server/internal/library/captcha/captcha.go
Normal file
44
server/internal/library/captcha/captcha.go
Normal file
@@ -0,0 +1,44 @@
|
||||
// Package captcha
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2022 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
//
|
||||
package captcha
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/mojocn/base64Captcha"
|
||||
)
|
||||
|
||||
// GetVerifyImgString 生成验证码
|
||||
func GetVerifyImgString(ctx context.Context) (idKeyC string, base64stringC string) {
|
||||
driver := &base64Captcha.DriverString{
|
||||
Height: 80,
|
||||
Width: 240,
|
||||
//NoiseCount: 50,
|
||||
//ShowLineOptions: 20,
|
||||
Length: 4,
|
||||
Source: "abcdefghjkmnpqrstuvwxyz23456789",
|
||||
Fonts: []string{"chromohv.ttf"},
|
||||
}
|
||||
driver = driver.ConvertFonts()
|
||||
store := base64Captcha.DefaultMemStore
|
||||
c := base64Captcha.NewCaptcha(driver, store)
|
||||
idKeyC, base64stringC, err := c.Generate()
|
||||
if err != nil {
|
||||
g.Log().Error(ctx, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// VerifyString 验证输入的验证码是否正确
|
||||
func VerifyString(id, answer string) bool {
|
||||
driver := new(base64Captcha.DriverString)
|
||||
store := base64Captcha.DefaultMemStore
|
||||
c := base64Captcha.NewCaptcha(driver, store)
|
||||
answer = gstr.ToLower(answer)
|
||||
return c.Verify(id, answer, true)
|
||||
}
|
||||
324
server/internal/library/casbin/adapter.go
Normal file
324
server/internal/library/casbin/adapter.go
Normal file
@@ -0,0 +1,324 @@
|
||||
// Package casbin
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2022 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
//
|
||||
package casbin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/casbin/casbin/v2/model"
|
||||
"github.com/casbin/casbin/v2/persist"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"log"
|
||||
"math"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTableName = "hg_admin_role_casbin"
|
||||
dropPolicyTableSql = `DROP TABLE IF EXISTS %s`
|
||||
createPolicyTableSql = `
|
||||
CREATE TABLE IF NOT EXISTS %s (
|
||||
id bigint(20) NOT NULL AUTO_INCREMENT,
|
||||
p_type varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
|
||||
v0 varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
|
||||
v1 varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
|
||||
v2 varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
|
||||
v3 varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
|
||||
v4 varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
|
||||
v5 varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
|
||||
PRIMARY KEY (id) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = 'casbin权限表' ROW_FORMAT = Dynamic;
|
||||
`
|
||||
)
|
||||
|
||||
type (
|
||||
adapter struct {
|
||||
db gdb.DB
|
||||
table string
|
||||
}
|
||||
|
||||
policyColumns struct {
|
||||
ID string // ID
|
||||
PType string // PType
|
||||
V0 string // V0
|
||||
V1 string // V1
|
||||
V2 string // V2
|
||||
V3 string // V3
|
||||
V4 string // V4
|
||||
V5 string // V5
|
||||
}
|
||||
|
||||
// policy rule entity
|
||||
policyRule struct {
|
||||
ID int64 `orm:"id" json:"id"`
|
||||
PType string `orm:"p_type" json:"p_type"`
|
||||
V0 string `orm:"v0" json:"v0"`
|
||||
V1 string `orm:"v1" json:"v1"`
|
||||
V2 string `orm:"v2" json:"v2"`
|
||||
V3 string `orm:"v3" json:"v3"`
|
||||
V4 string `orm:"v4" json:"v4"`
|
||||
V5 string `orm:"v5" json:"v5"`
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalidDatabaseLink = errors.New("invalid database link")
|
||||
policyColumnsName = policyColumns{
|
||||
ID: "id",
|
||||
PType: "p_type",
|
||||
V0: "v0",
|
||||
V1: "v1",
|
||||
V2: "v2",
|
||||
V3: "v3",
|
||||
V4: "v4",
|
||||
V5: "v5",
|
||||
}
|
||||
)
|
||||
|
||||
// NewAdapter Create a casbin adapter
|
||||
func NewAdapter(link string) (adp *adapter, err error) {
|
||||
adp = &adapter{table: defaultTableName}
|
||||
config := strings.SplitN(link, ":", 2)
|
||||
|
||||
if len(config) != 2 {
|
||||
err = errInvalidDatabaseLink
|
||||
return
|
||||
}
|
||||
|
||||
if adp.db, err = gdb.New(gdb.ConfigNode{Type: config[0], Link: config[1]}); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = adp.createPolicyTable()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (a *adapter) model() *gdb.Model {
|
||||
return a.db.Model(a.table).Safe().Ctx(context.TODO())
|
||||
}
|
||||
|
||||
// create a policy table when it's not exists.
|
||||
func (a *adapter) createPolicyTable() (err error) {
|
||||
_, err = a.db.Exec(context.TODO(), fmt.Sprintf(createPolicyTableSql, a.table))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// drop policy table from the storage.
|
||||
func (a *adapter) dropPolicyTable() (err error) {
|
||||
_, err = a.db.Exec(context.TODO(), fmt.Sprintf(dropPolicyTableSql, a.table))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// LoadPolicy loads all policy rules from the storage.
|
||||
func (a *adapter) LoadPolicy(model model.Model) (err error) {
|
||||
log.Println("LoadPolicy...")
|
||||
var rules []policyRule
|
||||
|
||||
if err = a.model().Scan(&rules); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, rule := range rules {
|
||||
a.loadPolicyRule(rule, model)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SavePolicy Saves all policy rules to the storage.
|
||||
func (a *adapter) SavePolicy(model model.Model) (err error) {
|
||||
if err = a.dropPolicyTable(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = a.createPolicyTable(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
policyRules := make([]policyRule, 0)
|
||||
|
||||
for ptype, ast := range model["p"] {
|
||||
for _, rule := range ast.Policy {
|
||||
policyRules = append(policyRules, a.buildPolicyRule(ptype, rule))
|
||||
}
|
||||
}
|
||||
|
||||
for ptype, ast := range model["g"] {
|
||||
for _, rule := range ast.Policy {
|
||||
policyRules = append(policyRules, a.buildPolicyRule(ptype, rule))
|
||||
}
|
||||
}
|
||||
|
||||
if count := len(policyRules); count > 0 {
|
||||
if _, err = a.model().Insert(policyRules); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// AddPolicy adds a policy rule to the storage.
|
||||
func (a *adapter) AddPolicy(sec string, ptype string, rule []string) (err error) {
|
||||
_, err = a.model().Insert(a.buildPolicyRule(ptype, rule))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// AddPolicies adds policy rules to the storage.
|
||||
func (a *adapter) AddPolicies(sec string, ptype string, rules [][]string) (err error) {
|
||||
if len(rules) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
policyRules := make([]policyRule, 0, len(rules))
|
||||
|
||||
for _, rule := range rules {
|
||||
policyRules = append(policyRules, a.buildPolicyRule(ptype, rule))
|
||||
}
|
||||
|
||||
_, err = a.model().Insert(policyRules)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// RemovePolicy removes a policy rule from the storage.
|
||||
func (a *adapter) RemovePolicy(sec string, ptype string, rule []string) (err error) {
|
||||
db := a.model()
|
||||
db = db.Where(policyColumnsName.PType, ptype)
|
||||
for index := 0; index < len(rule); index++ {
|
||||
db = db.Where(fmt.Sprintf("v%d", index), rule[index])
|
||||
}
|
||||
_, err = db.Delete()
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoveFilteredPolicy removes policy rules that match the filter from the storage.
|
||||
func (a *adapter) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) (err error) {
|
||||
db := a.model()
|
||||
db = db.Where(policyColumnsName.PType, ptype)
|
||||
for index := 0; index <= 5; index++ {
|
||||
if fieldIndex <= index && index < fieldIndex+len(fieldValues) {
|
||||
db = db.Where(fmt.Sprintf("v%d", index), fieldValues[index-fieldIndex])
|
||||
}
|
||||
}
|
||||
_, err = db.Delete()
|
||||
return
|
||||
}
|
||||
|
||||
// RemovePolicies removes policy rules from the storage (implements the persist.BatchAdapter interface).
|
||||
func (a *adapter) RemovePolicies(sec string, ptype string, rules [][]string) (err error) {
|
||||
db := a.model()
|
||||
|
||||
for _, rule := range rules {
|
||||
where := map[string]interface{}{policyColumnsName.PType: ptype}
|
||||
|
||||
for i := 0; i <= 5; i++ {
|
||||
if len(rule) > i {
|
||||
where[fmt.Sprintf("v%d", i)] = rule[i]
|
||||
}
|
||||
}
|
||||
|
||||
db = db.WhereOr(where)
|
||||
}
|
||||
|
||||
_, err = db.Delete()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// UpdatePolicy updates a policy rule from storage.
|
||||
func (a *adapter) UpdatePolicy(sec string, ptype string, oldRule, newRule []string) (err error) {
|
||||
_, err = a.model().Update(a.buildPolicyRule(ptype, newRule), a.buildPolicyRule(ptype, oldRule))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// UpdatePolicies updates some policy rules to storage, like db, redis.
|
||||
func (a *adapter) UpdatePolicies(sec string, ptype string, oldRules, newRules [][]string) (err error) {
|
||||
if len(oldRules) == 0 || len(newRules) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
err = a.db.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error {
|
||||
for i := 0; i < int(math.Min(float64(len(oldRules)), float64(len(newRules)))); i++ {
|
||||
if _, err = tx.Model(a.table).Update(a.buildPolicyRule(ptype, newRules[i]), a.buildPolicyRule(ptype, oldRules[i])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 加载策略规则
|
||||
func (a *adapter) loadPolicyRule(rule policyRule, model model.Model) {
|
||||
ruleText := rule.PType
|
||||
|
||||
if rule.V0 != "" {
|
||||
ruleText += ", " + rule.V0
|
||||
}
|
||||
|
||||
if rule.V1 != "" {
|
||||
ruleText += ", " + rule.V1
|
||||
}
|
||||
|
||||
if rule.V2 != "" {
|
||||
ruleText += ", " + rule.V2
|
||||
}
|
||||
|
||||
if rule.V3 != "" {
|
||||
ruleText += ", " + rule.V3
|
||||
}
|
||||
|
||||
if rule.V4 != "" {
|
||||
ruleText += ", " + rule.V4
|
||||
}
|
||||
|
||||
if rule.V5 != "" {
|
||||
ruleText += ", " + rule.V5
|
||||
}
|
||||
|
||||
persist.LoadPolicyLine(ruleText, model)
|
||||
}
|
||||
|
||||
// 构建策略规则
|
||||
func (a *adapter) buildPolicyRule(ptype string, data []string) policyRule {
|
||||
rule := policyRule{PType: ptype}
|
||||
|
||||
if len(data) > 0 {
|
||||
rule.V0 = data[0]
|
||||
}
|
||||
|
||||
if len(data) > 1 {
|
||||
rule.V1 = data[1]
|
||||
}
|
||||
|
||||
if len(data) > 2 {
|
||||
rule.V2 = data[2]
|
||||
}
|
||||
|
||||
if len(data) > 3 {
|
||||
rule.V3 = data[3]
|
||||
}
|
||||
|
||||
if len(data) > 4 {
|
||||
rule.V4 = data[4]
|
||||
}
|
||||
|
||||
if len(data) > 5 {
|
||||
rule.V5 = data[5]
|
||||
}
|
||||
|
||||
return rule
|
||||
}
|
||||
68
server/internal/library/casbin/casbin_test.go
Normal file
68
server/internal/library/casbin/casbin_test.go
Normal file
@@ -0,0 +1,68 @@
|
||||
// Package casbin
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2022 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
//
|
||||
package casbin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestNew description
|
||||
func TestNew(t *testing.T) {
|
||||
|
||||
InitEnforcer(context.TODO())
|
||||
|
||||
user := "admin"
|
||||
path := "/"
|
||||
method := http.MethodGet
|
||||
t.Logf("\nuser:%v\npath:%v\nmethod:%v", user, path, method)
|
||||
|
||||
ok, err := Enforcer.DeletePermissionsForUser(user)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Logf("delete user premission:%v", ok)
|
||||
CheckPremission(t, user, path, method)
|
||||
AddPremission(t, user, "*", ActionAll)
|
||||
CheckPremission(t, user, path, method)
|
||||
|
||||
user1 := "user1"
|
||||
path1 := "/api/v1/*"
|
||||
checkPathTrue := "/api/v1/user/list"
|
||||
checkPathFalse := "/api/v2/user/list"
|
||||
AddPremission(t, user1, path1, ActionAll)
|
||||
CheckPremission(t, user1, checkPathTrue, ActionPost)
|
||||
CheckPremission(t, user1, checkPathFalse, http.MethodGet)
|
||||
CheckPremission(t, user1, checkPathTrue, http.MethodGet)
|
||||
CheckPremission(t, user1, "/api/v1/user/list2", http.MethodGet)
|
||||
|
||||
ok, err = Enforcer.DeletePermissionsForUser(user1)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Logf("delete user premission:%v", ok)
|
||||
CheckPremission(t, user1, "/api/v1/user1/list", http.MethodGet)
|
||||
}
|
||||
|
||||
// CheckPremission description
|
||||
func CheckPremission(t *testing.T, user string, path string, method string) {
|
||||
ok, err := Enforcer.Enforce(user, path, method)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Logf("check \tuser[%s] \tpremission[%s] \tpath[%s] \tallow[%v]", user, method, path, ok)
|
||||
}
|
||||
|
||||
// Add description
|
||||
func AddPremission(t *testing.T, user string, path string, method string) {
|
||||
ok, err := Enforcer.AddPolicy(user, path, method)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Logf("add \tuser[%s] \tpremission[%s] \tpath[%s] \tresult[%v]", user, method, path, ok)
|
||||
}
|
||||
113
server/internal/library/casbin/enforcer.go
Normal file
113
server/internal/library/casbin/enforcer.go
Normal file
@@ -0,0 +1,113 @@
|
||||
// Package casbin
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2022 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
//
|
||||
package casbin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/casbin/casbin/v2"
|
||||
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"hotgo/internal/consts"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
ActionGet = http.MethodGet
|
||||
ActionPost = http.MethodPost
|
||||
ActionPut = http.MethodPut
|
||||
ActionDelete = http.MethodDelete
|
||||
ActionAll = "GET|POST|PUT|DELETE|PATCH|OPTIONS|HEAD"
|
||||
)
|
||||
|
||||
var Enforcer *casbin.Enforcer
|
||||
|
||||
// InitEnforcer 初始化
|
||||
func InitEnforcer(ctx context.Context) {
|
||||
var (
|
||||
link, _ = g.Cfg().Get(ctx, "database.default.link")
|
||||
a, err = NewAdapter(link.String())
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
g.Log().Panicf(ctx, "casbin.NewAdapter err . %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Enforcer, err = casbin.NewEnforcer("./manifest/config/casbin.conf", a)
|
||||
if err != nil {
|
||||
g.Log().Panicf(ctx, "casbin.NewEnforcer err . %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
loadPermissions(ctx)
|
||||
}
|
||||
|
||||
func loadPermissions(ctx context.Context) {
|
||||
type Policy struct {
|
||||
Key string `json:"key"`
|
||||
Permissions string `json:"permissions"`
|
||||
}
|
||||
var (
|
||||
rules [][]string
|
||||
polices []*Policy
|
||||
err error
|
||||
superRoleKey, _ = g.Cfg().Get(ctx, "hotgo.admin.superRoleKey")
|
||||
)
|
||||
|
||||
err = g.Model("hg_admin_role r").
|
||||
LeftJoin("hg_admin_role_menu rm", "r.id=rm.role_id").
|
||||
LeftJoin("hg_admin_menu m", "rm.menu_id=m.id").
|
||||
Fields("r.key,m.permissions").
|
||||
Where("r.status", consts.StatusEnabled).
|
||||
Where("m.status", consts.StatusEnabled).
|
||||
Where("m.permissions !=?", "").
|
||||
Where("r.key !=?", superRoleKey.String()).
|
||||
Scan(&polices)
|
||||
if err != nil {
|
||||
g.Log().Fatalf(ctx, "loadPermissions Scan err:%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, policy := range polices {
|
||||
if strings.Contains(policy.Permissions, ",") {
|
||||
lst := strings.Split(policy.Permissions, ",")
|
||||
for _, permissions := range lst {
|
||||
rules = append(rules, []string{policy.Key, permissions, ActionAll})
|
||||
}
|
||||
} else {
|
||||
rules = append(rules, []string{policy.Key, policy.Permissions, ActionAll})
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = Enforcer.AddPolicies(rules); err != nil {
|
||||
g.Log().Fatalf(ctx, "loadPermissions AddPolicies err:%v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func Clear(ctx context.Context) (err error) {
|
||||
_, err = Enforcer.RemovePolicies(Enforcer.GetPolicy())
|
||||
if err != nil {
|
||||
g.Log().Warningf(ctx, "Enforcer RemovePolicies err:%+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否清理干净
|
||||
if len(Enforcer.GetPolicy()) > 0 {
|
||||
return Clear(ctx)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func Refresh(ctx context.Context) (err error) {
|
||||
if err = Clear(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
loadPermissions(ctx)
|
||||
return
|
||||
}
|
||||
81
server/internal/library/contexts/context.go
Normal file
81
server/internal/library/contexts/context.go
Normal file
@@ -0,0 +1,81 @@
|
||||
// Package contexts
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2022 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
//
|
||||
package contexts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/model"
|
||||
)
|
||||
|
||||
// Init 初始化上下文对象指针到上下文对象中,以便后续的请求流程中可以修改
|
||||
func Init(r *ghttp.Request, customCtx *model.Context) {
|
||||
r.SetCtxVar(consts.ContextKey, customCtx)
|
||||
}
|
||||
|
||||
// Get 获得上下文变量,如果没有设置,那么返回nil
|
||||
func Get(ctx context.Context) *model.Context {
|
||||
value := ctx.Value(consts.ContextKey)
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
if localCtx, ok := value.(*model.Context); ok {
|
||||
return localCtx
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetUser 将上下文信息设置到上下文请求中,注意是完整覆盖
|
||||
func SetUser(ctx context.Context, user *model.Identity) {
|
||||
Get(ctx).User = user
|
||||
}
|
||||
|
||||
// SetResponse 设置组件响应 用于全局日志使用
|
||||
func SetResponse(ctx context.Context, response *model.Response) {
|
||||
Get(ctx).Response = response
|
||||
}
|
||||
|
||||
// SetModule 设置应用模块
|
||||
func SetModule(ctx context.Context, module string) {
|
||||
Get(ctx).Module = module
|
||||
}
|
||||
|
||||
// SetTakeUpTime 设置请求耗时
|
||||
func SetTakeUpTime(ctx context.Context, takeUpTime int64) {
|
||||
Get(ctx).TakeUpTime = takeUpTime
|
||||
}
|
||||
|
||||
// GetUserId 获取用户ID
|
||||
func GetUserId(ctx context.Context) int64 {
|
||||
user := Get(ctx).User
|
||||
if user == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return user.Id
|
||||
}
|
||||
|
||||
// GetRoleId 获取用户角色ID
|
||||
func GetRoleId(ctx context.Context) int64 {
|
||||
user := Get(ctx).User
|
||||
if user == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return user.Role
|
||||
}
|
||||
|
||||
// GetRoleKey 获取用户角色唯一编码
|
||||
func GetRoleKey(ctx context.Context) string {
|
||||
user := Get(ctx).User
|
||||
if user == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return user.RoleKey
|
||||
}
|
||||
70
server/internal/library/ems/ems.go
Normal file
70
server/internal/library/ems/ems.go
Normal file
@@ -0,0 +1,70 @@
|
||||
// Package ems
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2022 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
//
|
||||
package ems
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"hotgo/internal/model"
|
||||
"hotgo/utility/validate"
|
||||
"net/smtp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Send 发送邮件入口
|
||||
func Send(config *model.EmailConfig, to string, subject string, body string) error {
|
||||
return sendToMail(config, to, subject, body, "html")
|
||||
}
|
||||
|
||||
// SendTestMail 发送测试邮件
|
||||
func SendTestMail(config *model.EmailConfig, to string) error {
|
||||
subject := "这是一封来自HotGo的测试邮件"
|
||||
body := `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="iso-8859-15">
|
||||
<title>这是一封来自HotGo的测试邮件</title>
|
||||
</head>
|
||||
<body>
|
||||
当你收到这封邮件的时候,说明已经联调成功了,恭喜你!
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
return Send(config, to, subject, body)
|
||||
}
|
||||
|
||||
func sendToMail(config *model.EmailConfig, to, subject, body, mailType string) error {
|
||||
|
||||
if config == nil {
|
||||
return gerror.New("邮件配置不能为空")
|
||||
}
|
||||
var (
|
||||
contentType string
|
||||
auth = smtp.PlainAuth("", config.User, config.Password, config.Host)
|
||||
sendTo = strings.Split(to, ";")
|
||||
)
|
||||
|
||||
if len(sendTo) == 0 {
|
||||
return gerror.New("收件人不能为空")
|
||||
}
|
||||
|
||||
for _, em := range sendTo {
|
||||
if !validate.IsEmail(em) {
|
||||
return gerror.Newf("邮件格式不正确,请检查:%v", em)
|
||||
}
|
||||
}
|
||||
|
||||
if mailType == "html" {
|
||||
contentType = "Content-Type: text/" + mailType + "; charset=UTF-8"
|
||||
} else {
|
||||
contentType = "Content-Type: text/plain" + "; charset=UTF-8"
|
||||
}
|
||||
|
||||
msg := []byte("To: " + to + "\r\nFrom: " + config.SendName + "<" + config.User + ">" + "\r\nSubject: " + subject + "\r\n" + contentType + "\r\n\r\n" + body)
|
||||
|
||||
return smtp.SendMail(config.Addr, auth, config.User, sendTo, msg)
|
||||
}
|
||||
112
server/internal/library/jwt/jwt.go
Normal file
112
server/internal/library/jwt/jwt.go
Normal file
@@ -0,0 +1,112 @@
|
||||
// Package jwt
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2022 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
//
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
j "github.com/dgrijalva/jwt-go"
|
||||
"github.com/gogf/gf/v2/crypto/gmd5"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/library/cache"
|
||||
"hotgo/internal/model"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GenerateLoginToken 为指定用户生成token
|
||||
func GenerateLoginToken(ctx context.Context, user *model.Identity, isRefresh bool) (interface{}, error) {
|
||||
var (
|
||||
jwtVersion, _ = g.Cfg().Get(ctx, "jwt.version", "1.0")
|
||||
jwtSign, _ = g.Cfg().Get(ctx, "jwt.sign", "hotGo")
|
||||
token = j.NewWithClaims(j.SigningMethodHS256, j.MapClaims{
|
||||
"id": user.Id,
|
||||
"username": user.Username,
|
||||
"realname": user.RealName,
|
||||
"avatar": user.Avatar,
|
||||
"email": user.Email,
|
||||
"mobile": user.Mobile,
|
||||
"last_time": user.LastTime,
|
||||
"last_ip": user.LastIp,
|
||||
"exp": user.Exp,
|
||||
"expires": user.Expires,
|
||||
"app": user.App,
|
||||
"role": user.Role,
|
||||
"role_key": user.RoleKey,
|
||||
"visit_count": user.VisitCount,
|
||||
"is_refresh": isRefresh,
|
||||
"jwt_version": jwtVersion.String(),
|
||||
})
|
||||
)
|
||||
|
||||
tokenString, err := token.SignedString(jwtSign.Bytes())
|
||||
if err != nil {
|
||||
return nil, gerror.New(err.Error())
|
||||
}
|
||||
|
||||
var (
|
||||
tokenStringMd5 = gmd5.MustEncryptString(tokenString)
|
||||
// 绑定登录token
|
||||
c = cache.New()
|
||||
key = consts.RedisJwtToken + tokenStringMd5
|
||||
// 将有效期转为持续时间,单位:秒
|
||||
expires, _ = time.ParseDuration(fmt.Sprintf("+%vs", user.Expires))
|
||||
)
|
||||
|
||||
err = c.Set(ctx, key, tokenString, expires)
|
||||
if err != nil {
|
||||
return nil, gerror.New(err.Error())
|
||||
}
|
||||
|
||||
err = c.Set(ctx, consts.RedisJwtUserBind+user.App+":"+gconv.String(user.Id), key, expires)
|
||||
if err != nil {
|
||||
return nil, gerror.New(err.Error())
|
||||
}
|
||||
return tokenString, err
|
||||
}
|
||||
|
||||
// ParseToken 解析token
|
||||
func ParseToken(tokenString string, secret []byte) (j.MapClaims, error) {
|
||||
if tokenString == "" {
|
||||
err := gerror.New("token 为空")
|
||||
return nil, err
|
||||
}
|
||||
token, err := j.Parse(tokenString, func(token *j.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*j.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
return secret, nil
|
||||
})
|
||||
|
||||
if token == nil {
|
||||
err := gerror.New("token不存在")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if claims, ok := token.Claims.(j.MapClaims); ok && token.Valid {
|
||||
return claims, nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// GetAuthorization 获取authorization
|
||||
func GetAuthorization(r *ghttp.Request) string {
|
||||
// 默认从请求头获取
|
||||
var authorization = r.Header.Get("Authorization")
|
||||
|
||||
// 如果请求头不存在则从get参数获取
|
||||
if authorization == "" {
|
||||
return r.Get("authorization").String()
|
||||
}
|
||||
|
||||
return gstr.Replace(authorization, "Bearer ", "")
|
||||
}
|
||||
197
server/internal/library/location/location.go
Normal file
197
server/internal/library/location/location.go
Normal file
@@ -0,0 +1,197 @@
|
||||
// Package location
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2022 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
//
|
||||
package location
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/axgle/mahonia"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/kayon/iploc"
|
||||
"hotgo/utility/validate"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type IpLocationData struct {
|
||||
Ip string `json:"ip"`
|
||||
Country string `json:"country"`
|
||||
Region string `json:"region"`
|
||||
Province string `json:"province"`
|
||||
ProvinceCode int64 `json:"province_code"`
|
||||
City string `json:"city"`
|
||||
CityCode int64 `json:"city_code"`
|
||||
Area string `json:"area"`
|
||||
AreaCode int64 `json:"area_code"`
|
||||
}
|
||||
|
||||
// WhoisLocation 通过Whois接口查询IP归属地
|
||||
func WhoisLocation(ctx context.Context, ip string) IpLocationData {
|
||||
|
||||
type whoisRegionData struct {
|
||||
Ip string `json:"ip"`
|
||||
Pro string `json:"pro" `
|
||||
ProCode string `json:"proCode" `
|
||||
City string `json:"city" `
|
||||
CityCode string `json:"cityCode"`
|
||||
Region string `json:"region"`
|
||||
RegionCode string `json:"regionCode"`
|
||||
Addr string `json:"addr"`
|
||||
Err string `json:"err"`
|
||||
}
|
||||
|
||||
if !validate.IsIp(ip) {
|
||||
return IpLocationData{}
|
||||
}
|
||||
|
||||
response, err := g.Client().Timeout(10*time.Second).Get(ctx, "http://whois.pconline.com.cn/ipJson.jsp?ip="+ip+"&json=true")
|
||||
if err != nil {
|
||||
err = gerror.New(err.Error())
|
||||
return IpLocationData{
|
||||
Ip: ip,
|
||||
}
|
||||
}
|
||||
|
||||
defer response.Close()
|
||||
|
||||
var enc mahonia.Decoder
|
||||
enc = mahonia.NewDecoder("gbk")
|
||||
data := enc.ConvertString(response.ReadAllString())
|
||||
whoisData := whoisRegionData{}
|
||||
if err := gconv.Struct(data, &whoisData); err != nil {
|
||||
err = gerror.New(err.Error())
|
||||
|
||||
g.Log().Print(ctx, "err:", err)
|
||||
return IpLocationData{
|
||||
Ip: ip,
|
||||
}
|
||||
}
|
||||
|
||||
return IpLocationData{
|
||||
Ip: whoisData.Ip,
|
||||
//Country string `json:"country"`
|
||||
Region: whoisData.Addr,
|
||||
Province: whoisData.Pro,
|
||||
ProvinceCode: gconv.Int64(whoisData.ProCode),
|
||||
City: whoisData.City,
|
||||
CityCode: gconv.Int64(whoisData.CityCode),
|
||||
Area: whoisData.Region,
|
||||
AreaCode: gconv.Int64(whoisData.RegionCode),
|
||||
}
|
||||
}
|
||||
|
||||
// Cz88Find 通过Cz88的IP库查询IP归属地
|
||||
func Cz88Find(ctx context.Context, ip string) IpLocationData {
|
||||
if !validate.IsIp(ip) {
|
||||
g.Log().Print(ctx, "ip格式错误:", ip)
|
||||
return IpLocationData{}
|
||||
}
|
||||
|
||||
loc, err := iploc.OpenWithoutIndexes("./storage/ip/qqwry-utf8.dat")
|
||||
if err != nil {
|
||||
err = gerror.New(err.Error())
|
||||
return IpLocationData{
|
||||
Ip: ip,
|
||||
}
|
||||
}
|
||||
|
||||
detail := loc.Find(ip)
|
||||
if detail == nil {
|
||||
return IpLocationData{
|
||||
Ip: ip,
|
||||
}
|
||||
}
|
||||
|
||||
locationData := IpLocationData{
|
||||
Ip: ip,
|
||||
Country: detail.Country,
|
||||
Region: detail.Region,
|
||||
Province: detail.Province,
|
||||
City: detail.City,
|
||||
Area: detail.County,
|
||||
}
|
||||
|
||||
if gstr.LenRune(locationData.Province) == 0 {
|
||||
return locationData
|
||||
}
|
||||
|
||||
return locationData
|
||||
}
|
||||
|
||||
// IsJurisByIpTitle 判断地区名称是否为直辖市
|
||||
func IsJurisByIpTitle(title string) bool {
|
||||
|
||||
lists := []string{"北京市", "天津市", "重庆市", "上海市"}
|
||||
|
||||
for i := 0; i < len(lists); i++ {
|
||||
if gstr.Contains(lists[i], title) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetLocation 获取IP归属地信息
|
||||
func GetLocation(ctx context.Context, ip string) IpLocationData {
|
||||
method, _ := g.Cfg().Get(ctx, "hotgo.ipMethod", "cz88")
|
||||
|
||||
if method.String() == "whois" {
|
||||
return WhoisLocation(ctx, ip)
|
||||
}
|
||||
return Cz88Find(ctx, ip)
|
||||
}
|
||||
|
||||
// GetPublicIP 获取公网IP
|
||||
func GetPublicIP() (ip string, err error) {
|
||||
response, err := http.Get("http://members.3322.org/dyndns/getip")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
body, _ := ioutil.ReadAll(response.Body)
|
||||
ip = string(body)
|
||||
ip = strings.ReplaceAll(ip, "\n", "")
|
||||
return
|
||||
}
|
||||
|
||||
// GetLocalIP 获取服务器内网IP
|
||||
func GetLocalIP() (ip string, err error) {
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
ipAddr, ok := addr.(*net.IPNet)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if ipAddr.IP.IsLoopback() {
|
||||
continue
|
||||
}
|
||||
if !ipAddr.IP.IsGlobalUnicast() {
|
||||
continue
|
||||
}
|
||||
return ipAddr.IP.String(), nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetClientIp 获取客户端IP
|
||||
func GetClientIp(r *ghttp.Request) string {
|
||||
ip := r.Header.Get("X-Forwarded-For")
|
||||
if ip == "" {
|
||||
ip = r.GetClientIp()
|
||||
}
|
||||
return ip
|
||||
}
|
||||
83
server/internal/library/notify/feishu/client.go
Normal file
83
server/internal/library/notify/feishu/client.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package feishu
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
"hotgo/internal/library/notify/feishu/internal/security"
|
||||
)
|
||||
|
||||
const feishuAPI = "https://open.feishu.cn/open-apis/bot/v2/hook/"
|
||||
|
||||
// Client feishu client
|
||||
type Client struct {
|
||||
AccessToken string
|
||||
Secret string
|
||||
}
|
||||
|
||||
// NewClient new client
|
||||
func NewClient(accessToken, secret string) *Client {
|
||||
return &Client{
|
||||
AccessToken: accessToken,
|
||||
Secret: secret,
|
||||
}
|
||||
}
|
||||
|
||||
// Response response struct
|
||||
type Response struct {
|
||||
Code int64 `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data interface{} `json:"data"`
|
||||
|
||||
Extra interface{} `json:"Extra"`
|
||||
StatusCode int64 `json:"StatusCode"`
|
||||
StatusMessage string `json:"StatusMessage"`
|
||||
}
|
||||
|
||||
// Send send message
|
||||
func (d *Client) Send(message Message) (string, *Response, error) {
|
||||
res := &Response{}
|
||||
|
||||
if len(d.AccessToken) < 1 {
|
||||
return "", res, fmt.Errorf("accessToken is empty")
|
||||
}
|
||||
|
||||
timestamp := time.Now().Unix()
|
||||
sign, err := security.GenSign(d.Secret, timestamp)
|
||||
if err != nil {
|
||||
return "", res, err
|
||||
}
|
||||
|
||||
body := message.Body()
|
||||
body["timestamp"] = strconv.FormatInt(timestamp, 10)
|
||||
body["sign"] = sign
|
||||
|
||||
reqBytes, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return "", res, err
|
||||
}
|
||||
reqString := string(reqBytes)
|
||||
|
||||
client := resty.New()
|
||||
URL := fmt.Sprintf("%v%v", feishuAPI, d.AccessToken)
|
||||
resp, err := client.SetRetryCount(3).R().
|
||||
SetBody(body).
|
||||
SetHeader("Accept", "application/json").
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetResult(&Response{}).
|
||||
ForceContentType("application/json").
|
||||
Post(URL)
|
||||
|
||||
if err != nil {
|
||||
return reqString, nil, err
|
||||
}
|
||||
|
||||
result := resp.Result().(*Response)
|
||||
if result.Code != 0 {
|
||||
return reqString, result, fmt.Errorf("send message to feishu error = %s", result.Msg)
|
||||
}
|
||||
return reqString, result, nil
|
||||
}
|
||||
24
server/internal/library/notify/feishu/image.go
Normal file
24
server/internal/library/notify/feishu/image.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package feishu
|
||||
|
||||
type ImageMessage struct {
|
||||
MsgType MsgType `json:"msg_type"`
|
||||
Content ImageContent `json:"content"`
|
||||
}
|
||||
|
||||
type ImageContent struct {
|
||||
ImageKey string `json:"image_key"`
|
||||
}
|
||||
|
||||
func (m *ImageMessage) Body() map[string]interface{} {
|
||||
m.MsgType = MsgTypeImage
|
||||
return structToMap(m)
|
||||
}
|
||||
|
||||
func NewImageMessage() *ImageMessage {
|
||||
return &ImageMessage{}
|
||||
}
|
||||
|
||||
func (m *ImageMessage) SetImageKey(key string) *ImageMessage {
|
||||
m.Content.ImageKey = key
|
||||
return m
|
||||
}
|
||||
21
server/internal/library/notify/feishu/interactive.go
Normal file
21
server/internal/library/notify/feishu/interactive.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package feishu
|
||||
|
||||
type InteractiveMessage struct {
|
||||
MsgType MsgType `json:"msg_type"`
|
||||
Card string `json:"card"`
|
||||
}
|
||||
|
||||
func (m *InteractiveMessage) Body() map[string]interface{} {
|
||||
m.MsgType = MsgTypeInteractive
|
||||
return structToMap(m)
|
||||
}
|
||||
|
||||
func NewInteractiveMessage() *InteractiveMessage {
|
||||
return &InteractiveMessage{}
|
||||
}
|
||||
|
||||
// SetCard set card with cardbuilder https://open.feishu.cn/tool/cardbuilder?from=custom_bot_doc
|
||||
func (m *InteractiveMessage) SetCard(card string) *InteractiveMessage {
|
||||
m.Card = card
|
||||
return m
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package security
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// GenSign generate sign
|
||||
func GenSign(secret string, timestamp int64) (string, error) {
|
||||
stringToSign := fmt.Sprintf("%v", timestamp) + "\n" + secret
|
||||
var data []byte
|
||||
h := hmac.New(sha256.New, []byte(stringToSign))
|
||||
_, err := h.Write(data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
|
||||
return signature, nil
|
||||
}
|
||||
66
server/internal/library/notify/feishu/message.go
Normal file
66
server/internal/library/notify/feishu/message.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package feishu
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type MsgType string
|
||||
|
||||
// MsgType
|
||||
const (
|
||||
MsgTypeText MsgType = "text"
|
||||
MsgTypePost MsgType = "post"
|
||||
MsgTypeImage MsgType = "image"
|
||||
MsgTypeShareChat MsgType = "share_chat"
|
||||
MsgTypeInteractive MsgType = "interactive"
|
||||
)
|
||||
|
||||
// Message interface
|
||||
type Message interface {
|
||||
Body() map[string]interface{}
|
||||
}
|
||||
|
||||
func structToMap(item interface{}) map[string]interface{} {
|
||||
res := map[string]interface{}{}
|
||||
if item == nil {
|
||||
return res
|
||||
}
|
||||
v := reflect.TypeOf(item)
|
||||
reflectValue := reflect.ValueOf(item)
|
||||
reflectValue = reflect.Indirect(reflectValue)
|
||||
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
tag := v.Field(i).Tag.Get("json")
|
||||
|
||||
// remove omitEmpty
|
||||
omitEmpty := false
|
||||
if strings.HasSuffix(tag, "omitempty") {
|
||||
omitEmpty = true
|
||||
idx := strings.Index(tag, ",")
|
||||
if idx > 0 {
|
||||
tag = tag[:idx]
|
||||
} else {
|
||||
tag = ""
|
||||
}
|
||||
}
|
||||
|
||||
field := reflectValue.Field(i).Interface()
|
||||
if tag != "" && tag != "-" {
|
||||
if omitEmpty && reflectValue.Field(i).IsZero() {
|
||||
continue
|
||||
}
|
||||
|
||||
if v.Field(i).Type.Kind() == reflect.Struct {
|
||||
res[tag] = structToMap(field)
|
||||
} else {
|
||||
res[tag] = field
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
170
server/internal/library/notify/feishu/post.go
Normal file
170
server/internal/library/notify/feishu/post.go
Normal file
@@ -0,0 +1,170 @@
|
||||
package feishu
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
)
|
||||
|
||||
type PostMessage struct {
|
||||
MsgType MsgType `json:"msg_type"`
|
||||
Content PostContent `json:"content"`
|
||||
}
|
||||
|
||||
func NewPostMessage() *PostMessage {
|
||||
return &PostMessage{}
|
||||
}
|
||||
|
||||
func (m *PostMessage) Body() map[string]interface{} {
|
||||
m.MsgType = MsgTypePost
|
||||
return structToMap(m)
|
||||
}
|
||||
|
||||
func (m *PostMessage) SetZH(u PostUnit) *PostMessage {
|
||||
m.Content.Post.ZH = u
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *PostMessage) SetZHTitle(t string) *PostMessage {
|
||||
m.Content.Post.ZH.Title = t
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *PostMessage) AppendZHContent(i []PostItem) *PostMessage {
|
||||
m.Content.Post.ZH.Content = append(m.Content.Post.ZH.Content, i)
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *PostMessage) SetJA(u PostUnit) *PostMessage {
|
||||
m.Content.Post.JA = u
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *PostMessage) SetJATitle(t string) *PostMessage {
|
||||
m.Content.Post.JA.Title = t
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *PostMessage) AppendJAContent(i []PostItem) *PostMessage {
|
||||
m.Content.Post.JA.Content = append(m.Content.Post.JA.Content, i)
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *PostMessage) SetEN(u PostUnit) *PostMessage {
|
||||
m.Content.Post.EN = u
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *PostMessage) SetENTitle(t string) *PostMessage {
|
||||
m.Content.Post.EN.Title = t
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *PostMessage) AppendENContent(i []PostItem) *PostMessage {
|
||||
m.Content.Post.EN.Content = append(m.Content.Post.EN.Content, i)
|
||||
return m
|
||||
}
|
||||
|
||||
type PostContent struct {
|
||||
Post PostBody `json:"post"`
|
||||
}
|
||||
|
||||
type PostBody struct {
|
||||
ZH PostUnit `json:"zh_cn,omitempty"`
|
||||
JA PostUnit `json:"ja_jp,omitempty"`
|
||||
EN PostUnit `json:"en_us,omitempty"`
|
||||
}
|
||||
|
||||
type PostUnit struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
Content [][]PostItem `json:"content"`
|
||||
}
|
||||
|
||||
type PostItem interface{}
|
||||
|
||||
type Text struct {
|
||||
Tag string `json:"tag"`
|
||||
Text string `json:"text"`
|
||||
UnEscape bool `json:"un_escape,omitempty"`
|
||||
}
|
||||
|
||||
func NewText(text string) Text {
|
||||
t := Text{
|
||||
Tag: "text",
|
||||
Text: text,
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
type A struct {
|
||||
Tag string `json:"tag"`
|
||||
Text string `json:"text"`
|
||||
Href string `json:"href"`
|
||||
UnEscape bool `json:"un_escape,omitempty"`
|
||||
}
|
||||
|
||||
func NewA(text, href string) A {
|
||||
t := A{
|
||||
Tag: "a",
|
||||
Text: text,
|
||||
Href: href,
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
type AT struct {
|
||||
Tag string `json:"tag"`
|
||||
UserID string `json:"user_id"`
|
||||
}
|
||||
|
||||
func NewAT(userID string) AT {
|
||||
t := AT{
|
||||
Tag: "at",
|
||||
UserID: userID,
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
type Image struct {
|
||||
Tag string `json:"tag"`
|
||||
ImageKey string `json:"image_key"`
|
||||
Height int `json:"height"`
|
||||
Width int `json:"width"`
|
||||
}
|
||||
|
||||
func NewImage(imageKey string, height, width int) Image {
|
||||
t := Image{
|
||||
Tag: "image",
|
||||
ImageKey: imageKey,
|
||||
Height: height,
|
||||
Width: width,
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
type PostCMDMessage struct {
|
||||
MsgType MsgType `json:"msg_type"`
|
||||
Content PostCMDContent `json:"content"`
|
||||
}
|
||||
|
||||
func (m *PostCMDMessage) Body() map[string]interface{} {
|
||||
m.MsgType = MsgTypePost
|
||||
return structToMap(m)
|
||||
}
|
||||
|
||||
type PostCMDContent struct {
|
||||
Post map[string]interface{} `json:"post"`
|
||||
}
|
||||
|
||||
func NewPostCMDMessage() *PostCMDMessage {
|
||||
return &PostCMDMessage{}
|
||||
}
|
||||
|
||||
func (m *PostCMDMessage) SetPost(post string) *PostCMDMessage {
|
||||
var result map[string]interface{}
|
||||
err := json.Unmarshal([]byte(post), &result)
|
||||
if err != nil {
|
||||
log.Print("SetPost err: ", err)
|
||||
}
|
||||
m.Content.Post = result
|
||||
return m
|
||||
}
|
||||
24
server/internal/library/notify/feishu/sharechat.go
Normal file
24
server/internal/library/notify/feishu/sharechat.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package feishu
|
||||
|
||||
type ShareChatMessage struct {
|
||||
MsgType MsgType `json:"msg_type"`
|
||||
Content ShareChatContent `json:"content"`
|
||||
}
|
||||
|
||||
type ShareChatContent struct {
|
||||
ShareChatID string `json:"share_chat_id"`
|
||||
}
|
||||
|
||||
func (m *ShareChatMessage) Body() map[string]interface{} {
|
||||
m.MsgType = MsgTypeShareChat
|
||||
return structToMap(m)
|
||||
}
|
||||
|
||||
func NewShareChatMessage() *ShareChatMessage {
|
||||
return &ShareChatMessage{}
|
||||
}
|
||||
|
||||
func (m *ShareChatMessage) SetShareChatID(ID string) *ShareChatMessage {
|
||||
m.Content.ShareChatID = ID
|
||||
return m
|
||||
}
|
||||
24
server/internal/library/notify/feishu/text.go
Normal file
24
server/internal/library/notify/feishu/text.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package feishu
|
||||
|
||||
type TextMessage struct {
|
||||
MsgType MsgType `json:"msg_type"`
|
||||
Content Content `json:"content"`
|
||||
}
|
||||
|
||||
type Content struct {
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
func (m *TextMessage) Body() map[string]interface{} {
|
||||
m.MsgType = MsgTypeText
|
||||
return structToMap(m)
|
||||
}
|
||||
|
||||
func NewTextMessage() *TextMessage {
|
||||
return &TextMessage{}
|
||||
}
|
||||
|
||||
func (m *TextMessage) SetText(text string) *TextMessage {
|
||||
m.Content.Text = text
|
||||
return m
|
||||
}
|
||||
216
server/internal/library/queue/init.go
Normal file
216
server/internal/library/queue/init.go
Normal file
@@ -0,0 +1,216 @@
|
||||
// Package queue
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2022 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
//
|
||||
package queue
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"hotgo/utility/charset"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MqProducer interface {
|
||||
SendMsg(topic string, body string) (mqMsg MqMsg, err error)
|
||||
SendByteMsg(topic string, body []byte) (mqMsg MqMsg, err error)
|
||||
}
|
||||
|
||||
type MqConsumer interface {
|
||||
ListenReceiveMsgDo(topic string, receiveDo func(mqMsg MqMsg)) (err error)
|
||||
}
|
||||
|
||||
const (
|
||||
_ = iota
|
||||
SendMsg
|
||||
ReceiveMsg
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Switch bool `json:"switch"`
|
||||
Driver string `json:"driver"`
|
||||
Retry int `json:"retry"`
|
||||
MultiComsumer bool `json:"multiComsumer"`
|
||||
GroupName string `json:"groupName"`
|
||||
Redis RedisConf
|
||||
Rocketmq RocketmqConf
|
||||
Kafka KafkaConf
|
||||
}
|
||||
|
||||
type RedisConf struct {
|
||||
Address string `json:"address"`
|
||||
Db int `json:"db"`
|
||||
Pass string `json:"pass"`
|
||||
Timeout int `json:"timeout"`
|
||||
}
|
||||
type RocketmqConf struct {
|
||||
Address []string `json:"address"`
|
||||
LogLevel string `json:"logLevel"`
|
||||
}
|
||||
|
||||
type KafkaConf struct {
|
||||
Address []string `json:"address"`
|
||||
Version string `json:"version"`
|
||||
RandClient bool `json:"randClient"`
|
||||
}
|
||||
|
||||
type MqMsg struct {
|
||||
RunType int `json:"run_type"`
|
||||
Topic string `json:"topic"`
|
||||
MsgId string `json:"msg_id"`
|
||||
Offset int64 `json:"offset"`
|
||||
Partition int32 `json:"partition"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Body []byte `json:"body"`
|
||||
}
|
||||
|
||||
var (
|
||||
ctx = gctx.New()
|
||||
mqProducerInstanceMap map[string]MqProducer
|
||||
mqConsumerInstanceMap map[string]MqConsumer
|
||||
mutex sync.Mutex
|
||||
config Config
|
||||
)
|
||||
|
||||
func init() {
|
||||
mqProducerInstanceMap = make(map[string]MqProducer)
|
||||
mqConsumerInstanceMap = make(map[string]MqConsumer)
|
||||
get, err := g.Cfg().Get(ctx, "queue")
|
||||
if err != nil {
|
||||
g.Log().Fatalf(ctx, "queue config load fail, err .%v", err)
|
||||
return
|
||||
}
|
||||
get.Scan(&config)
|
||||
}
|
||||
|
||||
// InstanceConsumer 实例化消费者
|
||||
func InstanceConsumer() (mqClient MqConsumer, err error) {
|
||||
return NewConsumer(config.GroupName)
|
||||
}
|
||||
|
||||
// InstanceProducer 实例化生产者
|
||||
func InstanceProducer() (mqClient MqProducer, err error) {
|
||||
return NewProducer(config.GroupName)
|
||||
}
|
||||
|
||||
// NewProducer 新建一个生产者实例
|
||||
func NewProducer(groupName string) (mqClient MqProducer, err error) {
|
||||
if item, ok := mqProducerInstanceMap[groupName]; ok {
|
||||
return item, nil
|
||||
}
|
||||
|
||||
if groupName == "" {
|
||||
return mqClient, gerror.New("mq groupName is empty.")
|
||||
}
|
||||
|
||||
switch config.Driver {
|
||||
case "rocketmq":
|
||||
if len(config.Rocketmq.Address) == 0 {
|
||||
g.Log().Fatal(ctx, "queue rocketmq address is not support")
|
||||
}
|
||||
mqClient = RegisterRocketProducerMust(config.Rocketmq.Address, groupName, config.Retry)
|
||||
case "kafka":
|
||||
if len(config.Kafka.Address) == 0 {
|
||||
g.Log().Fatal(ctx, "queue kafka address is not support")
|
||||
}
|
||||
mqClient = RegisterKafkaProducerMust(KafkaConfig{
|
||||
Brokers: config.Kafka.Address,
|
||||
GroupID: groupName,
|
||||
Version: config.Kafka.Version,
|
||||
})
|
||||
case "redis":
|
||||
address, _ := g.Cfg().Get(ctx, "queue.redis.address", nil)
|
||||
if len(address.String()) == 0 {
|
||||
g.Log().Fatal(ctx, "queue redis address is not support")
|
||||
}
|
||||
mqClient = RegisterRedisMqProducerMust(RedisOption{
|
||||
Addr: config.Redis.Address,
|
||||
Passwd: config.Redis.Pass,
|
||||
DBnum: config.Redis.Db,
|
||||
Timeout: config.Redis.Timeout,
|
||||
}, PoolOption{
|
||||
5, 50, 5,
|
||||
}, groupName, config.Retry)
|
||||
|
||||
default:
|
||||
g.Log().Fatal(ctx, "queue driver is not support")
|
||||
}
|
||||
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
mqProducerInstanceMap[groupName] = mqClient
|
||||
|
||||
return mqClient, nil
|
||||
}
|
||||
|
||||
// NewConsumer 新建一个消费者实例
|
||||
func NewConsumer(groupName string) (mqClient MqConsumer, err error) {
|
||||
randTag := string(charset.RandomCreateBytes(6))
|
||||
|
||||
// 是否支持创建多个消费者
|
||||
if config.MultiComsumer == false {
|
||||
randTag = "001"
|
||||
}
|
||||
|
||||
if item, ok := mqConsumerInstanceMap[groupName+"-"+randTag]; ok {
|
||||
return item, nil
|
||||
}
|
||||
|
||||
if groupName == "" {
|
||||
return mqClient, gerror.New("mq groupName is empty.")
|
||||
}
|
||||
|
||||
switch config.Driver {
|
||||
case "rocketmq":
|
||||
if len(config.Rocketmq.Address) == 0 {
|
||||
return nil, gerror.New("queue.rocketmq.address is empty.")
|
||||
}
|
||||
mqClient = RegisterRocketConsumerMust(config.Rocketmq.Address, groupName)
|
||||
case "kafka":
|
||||
if len(config.Kafka.Address) == 0 {
|
||||
g.Log().Fatal(ctx, "queue kafka address is not support")
|
||||
}
|
||||
|
||||
clientId := "HOTGO-Consumer-" + groupName
|
||||
if config.Kafka.RandClient {
|
||||
clientId += "-" + randTag
|
||||
}
|
||||
|
||||
mqClient = RegisterKafkaMqConsumerMust(KafkaConfig{
|
||||
Brokers: config.Kafka.Address,
|
||||
GroupID: groupName,
|
||||
Version: config.Kafka.Version,
|
||||
ClientId: clientId,
|
||||
})
|
||||
case "redis":
|
||||
if len(config.Redis.Address) == 0 {
|
||||
g.Log().Fatal(ctx, "queue redis address is not support")
|
||||
}
|
||||
|
||||
mqClient = RegisterRedisMqConsumerMust(RedisOption{
|
||||
Addr: config.Redis.Address,
|
||||
Passwd: config.Redis.Pass,
|
||||
DBnum: config.Redis.Db,
|
||||
Timeout: config.Redis.Timeout,
|
||||
}, PoolOption{
|
||||
5, 50, 5,
|
||||
}, groupName)
|
||||
default:
|
||||
g.Log().Fatal(ctx, "queue driver is not support")
|
||||
}
|
||||
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
mqConsumerInstanceMap[groupName] = mqClient
|
||||
|
||||
return mqClient, nil
|
||||
}
|
||||
|
||||
// BodyString 返回消息体
|
||||
func (m *MqMsg) BodyString() string {
|
||||
return string(m.Body)
|
||||
}
|
||||
249
server/internal/library/queue/kafkamq.go
Normal file
249
server/internal/library/queue/kafkamq.go
Normal file
@@ -0,0 +1,249 @@
|
||||
// Package queue
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2022 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
//
|
||||
package queue
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/Shopify/sarama"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"hotgo/utility/signal"
|
||||
"time"
|
||||
)
|
||||
|
||||
type KafkaMq struct {
|
||||
endPoints []string
|
||||
Partitions int32
|
||||
producerIns sarama.AsyncProducer
|
||||
consumerIns sarama.ConsumerGroup
|
||||
}
|
||||
|
||||
type KafkaConfig struct {
|
||||
ClientId string
|
||||
Brokers []string
|
||||
GroupID string
|
||||
Partitions int32
|
||||
Replication int16
|
||||
Version string
|
||||
UserName string
|
||||
Password string
|
||||
}
|
||||
|
||||
// SendMsg 按字符串类型生产数据
|
||||
func (r *KafkaMq) SendMsg(topic string, body string) (mqMsg MqMsg, err error) {
|
||||
return r.SendByteMsg(topic, []byte(body))
|
||||
}
|
||||
|
||||
// SendByteMsg 生产数据
|
||||
func (r *KafkaMq) SendByteMsg(topic string, body []byte) (mqMsg MqMsg, err error) {
|
||||
msg := &sarama.ProducerMessage{
|
||||
Topic: topic,
|
||||
Value: sarama.ByteEncoder(body),
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
if r.producerIns == nil {
|
||||
return mqMsg, gerror.New("queue kafka producerIns is nil")
|
||||
}
|
||||
|
||||
r.producerIns.Input() <- msg
|
||||
ctx, cancle := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancle()
|
||||
|
||||
select {
|
||||
case info := <-r.producerIns.Successes():
|
||||
return MqMsg{
|
||||
RunType: SendMsg,
|
||||
Topic: info.Topic,
|
||||
Offset: info.Offset,
|
||||
Partition: info.Partition,
|
||||
Timestamp: info.Timestamp,
|
||||
}, nil
|
||||
case fail := <-r.producerIns.Errors():
|
||||
if nil != fail {
|
||||
return mqMsg, fail.Err
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return mqMsg, gerror.New("send mqMst timeout")
|
||||
}
|
||||
|
||||
return mqMsg, nil
|
||||
}
|
||||
|
||||
// ListenReceiveMsgDo 消费数据
|
||||
func (r *KafkaMq) ListenReceiveMsgDo(topic string, receiveDo func(mqMsg MqMsg)) (err error) {
|
||||
if r.consumerIns == nil {
|
||||
return gerror.New("queue kafka consumer not register")
|
||||
}
|
||||
|
||||
consumer := Consumer{
|
||||
ready: make(chan bool),
|
||||
receiveDoFun: receiveDo,
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
go func() {
|
||||
|
||||
for {
|
||||
if err := r.consumerIns.Consume(ctx, []string{topic}, &consumer); err != nil {
|
||||
FatalLog(ctx, "kafka Error from consumer", err)
|
||||
}
|
||||
|
||||
if ctx.Err() != nil {
|
||||
Log(ctx, fmt.Sprintf("kafka consoumer stop : %v", ctx.Err()))
|
||||
return
|
||||
}
|
||||
consumer.ready = make(chan bool)
|
||||
}
|
||||
}()
|
||||
|
||||
<-consumer.ready // Await till the consumer has been set up
|
||||
Log(ctx, "kafka consumer up and running!...")
|
||||
|
||||
signal.AppDefer(func() {
|
||||
Log(ctx, "kafka consumer close...")
|
||||
cancel()
|
||||
if err = r.consumerIns.Close(); err != nil {
|
||||
FatalLog(ctx, "kafka Error closing client", err)
|
||||
}
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// RegisterKafkaMqConsumerMust 注册消费者
|
||||
func RegisterKafkaMqConsumerMust(connOpt KafkaConfig) (client MqConsumer) {
|
||||
mqIns := &KafkaMq{}
|
||||
kfkVersion, _ := sarama.ParseKafkaVersion(connOpt.Version)
|
||||
if validateVersion(kfkVersion) == false {
|
||||
kfkVersion = sarama.V2_4_0_0
|
||||
}
|
||||
|
||||
brokers := connOpt.Brokers
|
||||
config := sarama.NewConfig()
|
||||
config.Consumer.Return.Errors = true
|
||||
config.Version = kfkVersion
|
||||
if connOpt.UserName != "" {
|
||||
config.Net.SASL.Enable = true
|
||||
config.Net.SASL.User = connOpt.UserName
|
||||
config.Net.SASL.Password = connOpt.Password
|
||||
}
|
||||
|
||||
// 默认按随机方式消费
|
||||
config.Consumer.Group.Rebalance.Strategy = sarama.BalanceStrategyRange
|
||||
config.Consumer.Offsets.Initial = sarama.OffsetNewest
|
||||
config.Consumer.Offsets.AutoCommit.Interval = 10 * time.Millisecond
|
||||
config.ClientID = connOpt.ClientId
|
||||
|
||||
consumerClient, err := sarama.NewConsumerGroup(brokers, connOpt.GroupID, config)
|
||||
if err != nil {
|
||||
g.Log().Fatal(ctx, err)
|
||||
}
|
||||
mqIns.consumerIns = consumerClient
|
||||
return mqIns
|
||||
}
|
||||
|
||||
// RegisterKafkaProducerMust 注册并启动生产者接口实现
|
||||
func RegisterKafkaProducerMust(connOpt KafkaConfig) (client MqProducer) {
|
||||
mqIns := &KafkaMq{}
|
||||
|
||||
connOpt.ClientId = "HOTGO-Producer"
|
||||
RegisterKafkaProducer(connOpt, mqIns) //这里如果使用go程需要处理chan同步问题
|
||||
|
||||
return mqIns
|
||||
}
|
||||
|
||||
// RegisterKafkaProducer 注册同步类型实例
|
||||
func RegisterKafkaProducer(connOpt KafkaConfig, mqIns *KafkaMq) {
|
||||
kfkVersion, _ := sarama.ParseKafkaVersion(connOpt.Version)
|
||||
if validateVersion(kfkVersion) == false {
|
||||
kfkVersion = sarama.V2_4_0_0
|
||||
}
|
||||
|
||||
brokers := connOpt.Brokers
|
||||
config := sarama.NewConfig()
|
||||
// 等待服务器所有副本都保存成功后的响应
|
||||
config.Producer.RequiredAcks = sarama.WaitForAll
|
||||
// 随机向partition发送消息
|
||||
config.Producer.Partitioner = sarama.NewRandomPartitioner
|
||||
// 是否等待成功和失败后的响应,只有上面的RequireAcks设置不是NoReponse这里才有用.
|
||||
config.Producer.Return.Successes = true
|
||||
|
||||
config.Producer.Return.Errors = true
|
||||
config.Producer.Compression = sarama.CompressionNone
|
||||
config.ClientID = connOpt.ClientId
|
||||
|
||||
config.Version = kfkVersion
|
||||
if connOpt.UserName != "" {
|
||||
config.Net.SASL.Enable = true
|
||||
config.Net.SASL.User = connOpt.UserName
|
||||
config.Net.SASL.Password = connOpt.Password
|
||||
}
|
||||
|
||||
var err error
|
||||
mqIns.producerIns, err = sarama.NewAsyncProducer(brokers, config)
|
||||
if err != nil {
|
||||
g.Log().Fatal(ctx, err)
|
||||
}
|
||||
|
||||
signal.AppDefer(func() {
|
||||
Log(ctx, "kafka producer AsyncClose...")
|
||||
mqIns.producerIns.AsyncClose()
|
||||
})
|
||||
}
|
||||
|
||||
// validateVersion 验证版本是否有效
|
||||
func validateVersion(version sarama.KafkaVersion) bool {
|
||||
for _, item := range sarama.SupportedVersions {
|
||||
if version.String() == item.String() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type Consumer struct {
|
||||
ready chan bool
|
||||
receiveDoFun func(mqMsg MqMsg)
|
||||
}
|
||||
|
||||
// Setup is run at the beginning of a new session, before ConsumeClaim
|
||||
func (consumer *Consumer) Setup(sarama.ConsumerGroupSession) error {
|
||||
// Mark the consumer as ready
|
||||
close(consumer.ready)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Cleanup is run at the end of a session, once all ConsumeClaim goroutines have exited
|
||||
func (consumer *Consumer) Cleanup(sarama.ConsumerGroupSession) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConsumeClaim must start a consumer loop of ConsumerGroupClaim's Messages().
|
||||
func (consumer *Consumer) ConsumeClaim(session sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error {
|
||||
|
||||
// NOTE:
|
||||
// Do not move the code below to a goroutine.
|
||||
// The `ConsumeClaim` itself is called within a goroutine, see:
|
||||
// https://github.com/Shopify/sarama/blob/master/consumer_group.go#L27-L29
|
||||
// `ConsumeClaim` 方法已经是 goroutine 调用 不要在该方法内进行 goroutine
|
||||
for message := range claim.Messages() {
|
||||
consumer.receiveDoFun(MqMsg{
|
||||
RunType: ReceiveMsg,
|
||||
Topic: message.Topic,
|
||||
Body: message.Value,
|
||||
Offset: message.Offset,
|
||||
Timestamp: message.Timestamp,
|
||||
Partition: message.Partition,
|
||||
})
|
||||
session.MarkMessage(message, "")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
42
server/internal/library/queue/logger.go
Normal file
42
server/internal/library/queue/logger.go
Normal file
@@ -0,0 +1,42 @@
|
||||
// Package queue
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2022 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
//
|
||||
package queue
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"hotgo/internal/consts"
|
||||
)
|
||||
|
||||
// ConsumerLog 消费日志
|
||||
func ConsumerLog(ctx context.Context, topic string, mqMsg MqMsg, err error) {
|
||||
if err != nil {
|
||||
g.Log(consts.QueueLogPath).Error(ctx, "消费 ["+topic+"] 失败", mqMsg, err)
|
||||
} else {
|
||||
g.Log(consts.QueueLogPath).Debug(ctx, "消费 ["+topic+"] 成功", mqMsg.MsgId)
|
||||
}
|
||||
}
|
||||
|
||||
// ProducerLog 生产日志
|
||||
func ProducerLog(ctx context.Context, topic string, data interface{}, err error) {
|
||||
if err != nil {
|
||||
g.Log(consts.QueueLogPath).Error(ctx, "生产 ["+topic+"] 失败", gconv.String(data))
|
||||
} else {
|
||||
g.Log(consts.QueueLogPath).Debug(ctx, "生产 ["+topic+"] 成功", gconv.String(data))
|
||||
}
|
||||
}
|
||||
|
||||
// FatalLog 致命日志
|
||||
func FatalLog(ctx context.Context, text string, err error) {
|
||||
g.Log(consts.QueueLogPath).Fatal(ctx, text+":", err)
|
||||
}
|
||||
|
||||
// Log 通用日志
|
||||
func Log(ctx context.Context, text string) {
|
||||
g.Log(consts.QueueLogPath).Debug(ctx, text)
|
||||
}
|
||||
289
server/internal/library/queue/redismq.go
Normal file
289
server/internal/library/queue/redismq.go
Normal file
@@ -0,0 +1,289 @@
|
||||
package queue
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/bufanyun/pool"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gomodule/redigo/redis"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/utility/encrypt"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RedisMq struct {
|
||||
poolName string
|
||||
groupName string
|
||||
retry int
|
||||
timeout int
|
||||
}
|
||||
|
||||
type PoolOption struct {
|
||||
InitCap int
|
||||
MaxCap int
|
||||
IdleTimeout int
|
||||
}
|
||||
|
||||
type RedisOption struct {
|
||||
Addr string
|
||||
Passwd string
|
||||
DBnum int
|
||||
Timeout int
|
||||
}
|
||||
|
||||
var redisPoolMap map[string]pool.Pool
|
||||
|
||||
func init() {
|
||||
redisPoolMap = make(map[string]pool.Pool)
|
||||
|
||||
}
|
||||
|
||||
// SendMsg 按字符串类型生产数据
|
||||
func (r *RedisMq) SendMsg(topic string, body string) (mqMsg MqMsg, err error) {
|
||||
return r.SendByteMsg(topic, []byte(body))
|
||||
}
|
||||
|
||||
// SendByteMsg 生产数据
|
||||
func (r *RedisMq) SendByteMsg(topic string, body []byte) (mqMsg MqMsg, err error) {
|
||||
if r.poolName == "" {
|
||||
return mqMsg, gerror.New("RedisMq producer not register")
|
||||
}
|
||||
if topic == "" {
|
||||
return mqMsg, gerror.New("RedisMq topic is empty")
|
||||
}
|
||||
|
||||
msgId := getRandMsgId()
|
||||
rdx, put, err := getRedis(r.poolName, r.retry)
|
||||
defer put()
|
||||
|
||||
if err != nil {
|
||||
return mqMsg, gerror.New(fmt.Sprint("queue redis 生产者获取redis实例失败:", err))
|
||||
}
|
||||
|
||||
mqMsg = MqMsg{
|
||||
RunType: SendMsg,
|
||||
Topic: topic,
|
||||
MsgId: msgId,
|
||||
Body: body,
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
mqMsgJson, err := json.Marshal(mqMsg)
|
||||
if err != nil {
|
||||
return mqMsg, gerror.New(fmt.Sprint("queue redis 生产者解析json消息失败:", err))
|
||||
}
|
||||
|
||||
queueName := r.genQueueName(r.groupName, topic)
|
||||
|
||||
_, err = redis.Int64(rdx.Do("LPUSH", queueName, mqMsgJson))
|
||||
if err != nil {
|
||||
return mqMsg, gerror.New(fmt.Sprint("queue redis 生产者添加消息失败:", err))
|
||||
}
|
||||
|
||||
if r.timeout > 0 {
|
||||
_, err = rdx.Do("EXPIRE", queueName, r.timeout)
|
||||
if err != nil {
|
||||
return mqMsg, gerror.New(fmt.Sprint("queue redis 生产者设置过期时间失败:", err))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ListenReceiveMsgDo 消费数据
|
||||
func (r *RedisMq) ListenReceiveMsgDo(topic string, receiveDo func(mqMsg MqMsg)) (err error) {
|
||||
if r.poolName == "" {
|
||||
return gerror.New("RedisMq producer not register")
|
||||
}
|
||||
if topic == "" {
|
||||
return gerror.New("RedisMq topic is empty")
|
||||
}
|
||||
|
||||
queueName := r.genQueueName(r.groupName, topic)
|
||||
go func() {
|
||||
for range time.Tick(500 * time.Millisecond) {
|
||||
mqMsgList := r.loopReadQueue(queueName)
|
||||
for _, mqMsg := range mqMsgList {
|
||||
receiveDo(mqMsg)
|
||||
}
|
||||
}
|
||||
}()
|
||||
select {}
|
||||
}
|
||||
|
||||
// 生成队列名称
|
||||
func (r *RedisMq) genQueueName(groupName string, topic string) string {
|
||||
return fmt.Sprintf(consts.QueueName+"%s_%s", groupName, topic)
|
||||
}
|
||||
|
||||
func (r *RedisMq) loopReadQueue(queueName string) (mqMsgList []MqMsg) {
|
||||
rdx, put, err := getRedis(r.poolName, r.retry)
|
||||
defer put()
|
||||
if err != nil {
|
||||
g.Log().Warningf(ctx, "loopReadQueue getRedis err:%+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
infoByte, err := redis.Bytes(rdx.Do("RPOP", queueName))
|
||||
if redis.ErrNil == err || len(infoByte) == 0 {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
g.Log().Warningf(ctx, "loopReadQueue redis RPOP err:%+v", err)
|
||||
break
|
||||
}
|
||||
|
||||
var mqMsg MqMsg
|
||||
if err = json.Unmarshal(infoByte, &mqMsg); err != nil {
|
||||
g.Log().Warningf(ctx, "loopReadQueue Unmarshal err:%+v", err)
|
||||
break
|
||||
}
|
||||
if mqMsg.MsgId != "" {
|
||||
mqMsgList = append(mqMsgList, mqMsg)
|
||||
}
|
||||
|
||||
}
|
||||
return mqMsgList
|
||||
}
|
||||
|
||||
func RegisterRedisMqProducerMust(connOpt RedisOption, poolOpt PoolOption, groupName string, retry int) (client MqProducer) {
|
||||
var err error
|
||||
client, err = RegisterRedisMq(connOpt, poolOpt, groupName, retry)
|
||||
if err != nil {
|
||||
g.Log().Fatal(ctx, "RegisterRedisMqProducerMust err:%+v", err)
|
||||
return
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
// RegisterRedisMqConsumerMust 注册消费者
|
||||
func RegisterRedisMqConsumerMust(connOpt RedisOption, poolOpt PoolOption, groupName string) (client MqConsumer) {
|
||||
var err error
|
||||
client, err = RegisterRedisMq(connOpt, poolOpt, groupName, 0)
|
||||
if err != nil {
|
||||
g.Log().Fatal(ctx, "RegisterRedisMqConsumerMust err:%+v", err)
|
||||
return
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
// RegisterRedisMq 注册redis实例
|
||||
func RegisterRedisMq(connOpt RedisOption, poolOpt PoolOption, groupName string, retry int) (mqIns *RedisMq, err error) {
|
||||
poolName, err := registerRedis(connOpt.Addr, connOpt.Passwd, connOpt.DBnum, poolOpt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if retry <= 0 {
|
||||
retry = 0
|
||||
}
|
||||
|
||||
mqIns = &RedisMq{
|
||||
poolName: poolName,
|
||||
groupName: groupName,
|
||||
retry: retry,
|
||||
timeout: connOpt.Timeout,
|
||||
}
|
||||
return mqIns, nil
|
||||
}
|
||||
|
||||
// RegisterRedis 注册一个redis配置
|
||||
func registerRedis(host, pass string, dbNum int, opt PoolOption) (poolName string, err error) {
|
||||
poolName = encrypt.Md5ToString(fmt.Sprintf("%s-%s-%d", host, pass, dbNum))
|
||||
if _, ok := redisPoolMap[poolName]; ok {
|
||||
return poolName, nil
|
||||
}
|
||||
|
||||
connRedis := func() (interface{}, error) {
|
||||
conn, err := redis.Dial("tcp", host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if pass != "" {
|
||||
if _, err := conn.Do("AUTH", pass); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if dbNum > 0 {
|
||||
if _, err := conn.Do("SELECT", dbNum); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return conn, err
|
||||
}
|
||||
|
||||
// closeRedis 关闭连接
|
||||
closeRedis := func(v interface{}) error {
|
||||
return v.(redis.Conn).Close()
|
||||
}
|
||||
|
||||
// pingRedis 检测连接连通性
|
||||
pingRedis := func(v interface{}) error {
|
||||
conn := v.(redis.Conn)
|
||||
val, err := redis.String(conn.Do("PING"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if val != "PONG" {
|
||||
return gerror.New("queue redis ping is error ping => " + val)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
p, err := pool.NewChannelPool(&pool.Config{
|
||||
InitialCap: opt.InitCap,
|
||||
MaxCap: opt.MaxCap,
|
||||
Factory: connRedis,
|
||||
Close: closeRedis,
|
||||
Ping: pingRedis,
|
||||
IdleTimeout: time.Duration(opt.IdleTimeout) * time.Second,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return poolName, err
|
||||
}
|
||||
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
redisPoolMap[poolName] = p
|
||||
return poolName, nil
|
||||
}
|
||||
|
||||
// getRedis 获取一个redis db连接
|
||||
func getRedis(poolName string, retry int) (db redis.Conn, put func(), err error) {
|
||||
put = func() {}
|
||||
if _, ok := redisPoolMap[poolName]; ok == false {
|
||||
return nil, put, gerror.New("db connect is nil")
|
||||
}
|
||||
redisPool := redisPoolMap[poolName]
|
||||
|
||||
conn, err := redisPool.Get()
|
||||
for i := 0; i < retry; i++ {
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
conn, err = redisPool.Get()
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, put, err
|
||||
}
|
||||
put = func() {
|
||||
redisPool.Put(conn)
|
||||
}
|
||||
|
||||
db = conn.(redis.Conn)
|
||||
return db, put, nil
|
||||
}
|
||||
|
||||
func getRandMsgId() (msgId string) {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
radium := rand.Intn(999) + 1
|
||||
timeCode := time.Now().UnixNano()
|
||||
|
||||
msgId = fmt.Sprintf("%d%.4d", timeCode, radium)
|
||||
return msgId
|
||||
}
|
||||
171
server/internal/library/queue/rocketmq.go
Normal file
171
server/internal/library/queue/rocketmq.go
Normal file
@@ -0,0 +1,171 @@
|
||||
// Package queue
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2022 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
//
|
||||
package queue
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/apache/rocketmq-client-go/v2"
|
||||
"github.com/apache/rocketmq-client-go/v2/consumer"
|
||||
"github.com/apache/rocketmq-client-go/v2/primitive"
|
||||
"github.com/apache/rocketmq-client-go/v2/producer"
|
||||
"github.com/apache/rocketmq-client-go/v2/rlog"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
type RocketMq struct {
|
||||
endPoints []string
|
||||
producerIns rocketmq.Producer
|
||||
consumerIns rocketmq.PushConsumer
|
||||
}
|
||||
|
||||
// rewriteLog 重写日志
|
||||
func rewriteLog() {
|
||||
level, _ := g.Cfg().Get(ctx, "queue.rocketmq.logLevel", "debug")
|
||||
rlog.SetLogger(&RocketMqLogger{Flag: "[rocket_mq]", LevelLog: level.String()})
|
||||
}
|
||||
|
||||
// RegisterRocketProducerMust 注册并启动生产者接口实现
|
||||
func RegisterRocketProducerMust(endPoints []string, groupName string, retry int) (client MqProducer) {
|
||||
rewriteLog()
|
||||
var err error
|
||||
client, err = RegisterRocketMqProducer(endPoints, groupName, retry)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
// RegisterRocketConsumerMust 注册消费者
|
||||
func RegisterRocketConsumerMust(endPoints []string, groupName string) (client MqConsumer) {
|
||||
rewriteLog()
|
||||
var err error
|
||||
client, err = RegisterRocketMqConsumer(endPoints, groupName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
// SendMsg 按字符串类型生产数据
|
||||
func (r *RocketMq) SendMsg(topic string, body string) (mqMsg MqMsg, err error) {
|
||||
return r.SendByteMsg(topic, []byte(body))
|
||||
}
|
||||
|
||||
// SendByteMsg 生产数据
|
||||
func (r *RocketMq) SendByteMsg(topic string, body []byte) (mqMsg MqMsg, err error) {
|
||||
if r.producerIns == nil {
|
||||
return mqMsg, errors.New("RocketMq producer not register")
|
||||
}
|
||||
|
||||
result, err := r.producerIns.SendSync(context.Background(), &primitive.Message{
|
||||
Topic: topic,
|
||||
Body: body,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if result.Status != primitive.SendOK {
|
||||
return mqMsg, errors.New(fmt.Sprintf("RocketMq producer send msg error status:%v", result.Status))
|
||||
}
|
||||
|
||||
mqMsg = MqMsg{
|
||||
RunType: SendMsg,
|
||||
Topic: topic,
|
||||
MsgId: result.MsgID,
|
||||
Body: body,
|
||||
}
|
||||
return mqMsg, nil
|
||||
}
|
||||
|
||||
// ListenReceiveMsgDo 消费数据
|
||||
func (r *RocketMq) ListenReceiveMsgDo(topic string, receiveDo func(mqMsg MqMsg)) (err error) {
|
||||
if r.consumerIns == nil {
|
||||
return errors.New("RocketMq consumer not register")
|
||||
}
|
||||
|
||||
err = r.consumerIns.Subscribe(topic, consumer.MessageSelector{}, func(ctx context.Context,
|
||||
msgs ...*primitive.MessageExt) (consumer.ConsumeResult, error) {
|
||||
for _, item := range msgs {
|
||||
go receiveDo(MqMsg{
|
||||
RunType: ReceiveMsg,
|
||||
Topic: item.Topic,
|
||||
MsgId: item.MsgId,
|
||||
Body: item.Body,
|
||||
})
|
||||
}
|
||||
return consumer.ConsumeSuccess, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = r.consumerIns.Start()
|
||||
if err != nil {
|
||||
r.consumerIns.Unsubscribe(topic)
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// RegisterRocketMqProducer 注册rocketmq生产者
|
||||
func RegisterRocketMqProducer(endPoints []string, groupName string, retry int) (mqIns *RocketMq, err error) {
|
||||
addr, err := primitive.NewNamesrvAddr(endPoints...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mqIns = &RocketMq{
|
||||
endPoints: endPoints,
|
||||
}
|
||||
|
||||
if retry <= 0 {
|
||||
retry = 0
|
||||
}
|
||||
|
||||
mqIns.producerIns, err = rocketmq.NewProducer(
|
||||
producer.WithNameServer(addr),
|
||||
producer.WithRetry(retry),
|
||||
producer.WithGroupName(groupName),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = mqIns.producerIns.Start()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mqIns, nil
|
||||
}
|
||||
|
||||
// RegisterRocketMqConsumer 注册rocketmq消费者
|
||||
func RegisterRocketMqConsumer(endPoints []string, groupName string) (mqIns *RocketMq, err error) {
|
||||
addr, err := primitive.NewNamesrvAddr(endPoints...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mqIns = &RocketMq{
|
||||
endPoints: endPoints,
|
||||
}
|
||||
mqIns.consumerIns, err = rocketmq.NewPushConsumer(
|
||||
consumer.WithNameServer(addr),
|
||||
consumer.WithConsumerModel(consumer.Clustering),
|
||||
consumer.WithGroupName(groupName),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mqIns, nil
|
||||
}
|
||||
89
server/internal/library/queue/rocketmq_logger.go
Normal file
89
server/internal/library/queue/rocketmq_logger.go
Normal file
@@ -0,0 +1,89 @@
|
||||
// Package queue
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2022 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
//
|
||||
package queue
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type RocketMqLogger struct {
|
||||
Flag string
|
||||
LevelLog string
|
||||
}
|
||||
|
||||
func (l *RocketMqLogger) Debug(msg string, fields map[string]interface{}) {
|
||||
if l.LevelLog == "close" {
|
||||
return
|
||||
}
|
||||
if msg == "" && len(fields) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if l.LevelLog == "debug" || l.LevelLog == "all" {
|
||||
Log(ctx, fmt.Sprint(l.Flag, " [debug] ", msg))
|
||||
}
|
||||
}
|
||||
|
||||
func (l *RocketMqLogger) Level(level string) {
|
||||
Log(ctx, fmt.Sprint(l.Flag, " [level] ", level))
|
||||
}
|
||||
|
||||
func (l *RocketMqLogger) OutputPath(path string) (err error) {
|
||||
Log(ctx, fmt.Sprint(l.Flag, " [path] ", path))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *RocketMqLogger) Info(msg string, fields map[string]interface{}) {
|
||||
if l.LevelLog == "close" {
|
||||
return
|
||||
}
|
||||
if msg == "" && len(fields) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if l.LevelLog == "info" || l.LevelLog == "all" {
|
||||
Log(ctx, fmt.Sprint(l.Flag, " [info] ", msg))
|
||||
}
|
||||
}
|
||||
|
||||
func (l *RocketMqLogger) Warning(msg string, fields map[string]interface{}) {
|
||||
if l.LevelLog == "close" {
|
||||
return
|
||||
}
|
||||
if msg == "" && len(fields) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if l.LevelLog == "warn" || l.LevelLog == "all" {
|
||||
Log(ctx, fmt.Sprint(l.Flag, " [warn] ", msg))
|
||||
}
|
||||
}
|
||||
|
||||
func (l *RocketMqLogger) Error(msg string, fields map[string]interface{}) {
|
||||
if l.LevelLog == "close" {
|
||||
return
|
||||
}
|
||||
if msg == "" && len(fields) == 0 {
|
||||
return
|
||||
}
|
||||
if l.LevelLog == "error" || l.LevelLog == "all" {
|
||||
Log(ctx, fmt.Sprint(l.Flag, " [error] ", msg))
|
||||
}
|
||||
}
|
||||
|
||||
func (l *RocketMqLogger) Fatal(msg string, fields map[string]interface{}) {
|
||||
if l.LevelLog == "close" {
|
||||
return
|
||||
}
|
||||
if msg == "" && len(fields) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if l.LevelLog == "fatal" || l.LevelLog == "all" {
|
||||
Log(ctx, fmt.Sprint(l.Flag, " [fatal] ", msg))
|
||||
}
|
||||
}
|
||||
84
server/internal/library/response/response.go
Normal file
84
server/internal/library/response/response.go
Normal file
@@ -0,0 +1,84 @@
|
||||
// Package response
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2022 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
//
|
||||
package response
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/library/contexts"
|
||||
"hotgo/internal/model"
|
||||
"time"
|
||||
)
|
||||
|
||||
// JsonExit 返回JSON数据并退出当前HTTP执行函数
|
||||
func JsonExit(r *ghttp.Request, code int, message string, data ...interface{}) {
|
||||
RJson(r, code, message, data...)
|
||||
r.Exit()
|
||||
}
|
||||
|
||||
// RJson 标准返回结果数据结构封装
|
||||
// @Description: 返回固定数据结构的JSON
|
||||
// @param r
|
||||
// @param code 状态码(200:成功,302跳转,和http请求状态码一至)
|
||||
// @param message 请求结果信息
|
||||
// @param data 请求结果,根据不同接口返回结果的数据结构不同
|
||||
//
|
||||
func RJson(r *ghttp.Request, code int, message string, data ...interface{}) {
|
||||
responseData := interface{}(nil)
|
||||
if len(data) > 0 {
|
||||
responseData = data[0]
|
||||
}
|
||||
Res := &model.Response{
|
||||
Code: code,
|
||||
Message: message,
|
||||
Timestamp: time.Now().Unix(),
|
||||
TraceID: gctx.CtxId(r.Context()),
|
||||
}
|
||||
|
||||
// 如果不是正常的返回,则将data转为error
|
||||
if consts.CodeOK == code {
|
||||
Res.Data = responseData
|
||||
} else {
|
||||
Res.Error = responseData
|
||||
}
|
||||
|
||||
// 清空响应
|
||||
r.Response.ClearBuffer()
|
||||
|
||||
// 写入响应
|
||||
r.Response.WriteJson(Res)
|
||||
|
||||
// 加入到上下文
|
||||
contexts.SetResponse(r.Context(), Res)
|
||||
}
|
||||
|
||||
// SusJson 返回成功JSON
|
||||
func SusJson(isExit bool, r *ghttp.Request, message string, data ...interface{}) {
|
||||
if isExit {
|
||||
JsonExit(r, consts.CodeOK, message, data...)
|
||||
}
|
||||
RJson(r, consts.CodeOK, message, data...)
|
||||
}
|
||||
|
||||
// FailJson 返回失败JSON
|
||||
func FailJson(isExit bool, r *ghttp.Request, message string, data ...interface{}) {
|
||||
if isExit {
|
||||
JsonExit(r, consts.CodeNil, message, data...)
|
||||
}
|
||||
RJson(r, consts.CodeNil, message, data...)
|
||||
}
|
||||
|
||||
// Redirect 重定向
|
||||
func Redirect(r *ghttp.Request, location string, code ...int) {
|
||||
r.Response.RedirectTo(location, code...)
|
||||
}
|
||||
|
||||
// Download 下载文件
|
||||
func Download(r *ghttp.Request, location string, code ...int) {
|
||||
r.Response.ServeFileDownload("test.txt")
|
||||
}
|
||||
Reference in New Issue
Block a user