go: Publish/Subscribe Pattern

 

go install github.com/peterbourgon/diskv/v3 
go install github.com/rabbitmq/amqp091-go 
go install github.com/rs/zerolog 
go install github.com/joho/godotenv 
go get github.com/mattn/go-sqlite3
go get github.com/google/uuid
go get github.com/rabbitmq/amqp091-go

//go get github.com/glebarez/sqlite

如果遇到不是WIN32程序时,终端命令行执行:
$env:CGO_ENABLED=0; go build -o bin/godesginpattern.exe .
go run main.go

  

项目结构:

image

 

/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:Publish/Subscribe Pattern 发布订阅模式
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : goLang 2024.3.6 go 26.2
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/6/24 20:20
# User      :  geovindu
# Product   : GoLand
# Project   : godesginpattern
# File      : settings.go
*/
package config

import (
	"github.com/joho/godotenv"
	"os"
	"strconv"
)

var (
	LogLevel      = "INFO"
	LogDir        = "./logs"
	LogRotateDays = 7

	DBPath = "./jewelry_msg.db"

	AsyncWorkerNum = 5
	UseRabbitMQ    = false

	RabbitHost  string
	RabbitPort  int
	RabbitUser  string
	RabbitPwd   string
	RabbitVhost = "/"
)

func InitConfig() {
	_ = godotenv.Load()
	RabbitHost = getEnv("RABBIT_HOST", "127.0.0.1")
	portStr := getEnv("RABBIT_PORT", "5672")
	RabbitPort, _ = strconv.Atoi(portStr)
	RabbitUser = getEnv("RABBIT_USER", "guest")
	RabbitPwd = getEnv("RABBIT_PWD", "guest")
}

func getEnv(key, def string) string {
	val := os.Getenv(key)
	if val == "" {
		return def
	}
	return val
}



/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:Publish/Subscribe Pattern 发布订阅模式
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : goLang 2024.3.6 go 26.2
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/6/24 20:28
# User      :  geovindu
# Product   : GoLand
# Project   : godesginpattern
# File      : topics.go
*/
package config

type Topic struct {
	RawMaterialPurchase string
	JewelryProduction   string
	QualityInspection   string
	StoreDistribution   string
	JewelrySale         string
	AfterSalesCare      string
}

var TopicConst = Topic{
	RawMaterialPurchase: "RawMaterialPurchase",
	JewelryProduction:   "JewelryProduction",
	QualityInspection:   "QualityInspection",
	StoreDistribution:   "StoreDistribution",
	JewelrySale:         "JewelrySale",
	AfterSalesCare:      "AfterSalesCare",
}


/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:Publish/Subscribe Pattern 发布订阅模式
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : goLang 2024.3.6 go 26.2
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/6/24 20:29
# User      :  geovindu
# Product   : GoLand
# Project   : godesginpattern
# File      : model.go
*/
package domain

import "encoding/json"

type JewelryMessage struct {
	Title      string `json:"title"`
	Content    string `json:"content"`
	Department string `json:"department"`
}

type RawMaterialPurchaseMsg struct {
	JewelryMessage
	Material string `json:"material"`
	Quantity string `json:"quantity"`
}

type JewelryProductionMsg struct {
	JewelryMessage
	ProductCount int    `json:"product_count"`
	ProductType  string `json:"product_type"`
}

type QualityInspectionMsg struct {
	JewelryMessage
	QualifiedCount int  `json:"qualified_count"`
	HasCertificate bool `json:"has_certificate"`
}

type StoreDistributionMsg struct {
	JewelryMessage
	StoreName       string `json:"store_name"`
	DistributeCount int    `json:"distribute_count"`
}

type JewelrySaleMsg struct {
	JewelryMessage
	ProductName string  `json:"product_name"`
	Amount      float64 `json:"amount"`
	Store       string  `json:"store"`
}

type AfterSalesCareMsg struct {
	JewelryMessage
	CustomerName string `json:"customer_name"`
	ServiceType  string `json:"service_type"`
}

func ToJson(v any) string {
	b, _ := json.MarshalIndent(v, "", "  ")
	return string(b)
}


/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:Publish/Subscribe Pattern 发布订阅模式
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : goLang 2024.3.6 go 26.2
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/6/24 20:30
# User      :  geovindu
# Product   : GoLand
# Project   : godesginpattern
# File      : log.go
*/
package logger

import (
	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
	"godesginpattern/publishsubscribe/config"
	"os"
	"path/filepath"
)

var Logger zerolog.Logger

func InitLogger() {
	_ = os.MkdirAll(config.LogDir, 0755)
	logPath := filepath.Join(config.LogDir, "jewelry_pubsub.log")
	f, _ := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
	multi := zerolog.MultiLevelWriter(os.Stdout, f)
	zerolog.TimeFieldFormat = "2006-01-02 15:04:05"
	Logger = zerolog.New(multi).With().Timestamp().Caller().Logger()
	log.Logger = Logger
}

  

/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:Publish/Subscribe Pattern 发布订阅模式
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : goLang 2024.3.6 go 26.2
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/6/24 20:38
# User      :  geovindu
# Product   : GoLand
# Project   : godesginpattern
# File      : async_publisher.go
*/
package messaging

import (
	"godesginpattern/publishsubscribe/config"
	"godesginpattern/publishsubscribe/logger"
	"sync"
)

// 统一两种发布方法的兼容包装:统一成无返回签名
type PublishFunc func(topic string, msg any)

type AsyncPublisher struct {
	workerNum int
	taskChan  chan func()
	wg        sync.WaitGroup
}

var AsyncPub = NewAsyncPublisher(config.AsyncWorkerNum)

func NewAsyncPublisher(worker int) *AsyncPublisher {
	ap := &AsyncPublisher{
		workerNum: worker,
		taskChan:  make(chan func(), 100),
	}
	for i := 0; i < worker; i++ {
		go ap.worker()
	}
	return ap
}

func (ap *AsyncPublisher) worker() {
	for task := range ap.taskChan {
		task()
		ap.wg.Done()
	}
}

func (ap *AsyncPublisher) PublishAsync(pub PublishFunc, topic string, msg any) {
	ap.wg.Add(1)
	ap.taskChan <- func() {
		defer func() {
			if r := recover(); r != nil {
				logger.Logger.Error().Any("panic", r).Str("topic", topic).Msg("async publish panic")
			}
		}()
		pub(topic, msg)
	}
}

func (ap *AsyncPublisher) WaitAndClose() {
	ap.wg.Wait()
	close(ap.taskChan)
}


/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:Publish/Subscribe Pattern 发布订阅模式
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : goLang 2024.3.6 go 26.2
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/6/24 20:39
# User      :  geovindu
# Product   : GoLand
# Project   : godesginpattern
# File      : broker.go
*/
package messaging

import (
	"godesginpattern/publishsubscribe/logger"
	"sync"
)

type SubCallback func(msg any)
type PubSubBroker struct {
	mu          sync.RWMutex
	subscribers map[string][]SubCallback
}

var LocalBroker = &PubSubBroker{
	subscribers: make(map[string][]SubCallback),
}

func (b *PubSubBroker) Subscribe(topic string, cb SubCallback) {
	b.mu.Lock()
	defer b.mu.Unlock()
	b.subscribers[topic] = append(b.subscribers[topic], cb)
	logger.Logger.Info().Str("topic", topic).Str("handler", getFuncName(cb)).Msg("subscribe register success")
}

func (b *PubSubBroker) UnSubscribe(topic string, cb SubCallback) {
	b.mu.Lock()
	defer b.mu.Unlock()
	list, ok := b.subscribers[topic]
	if !ok {
		return
	}
	newList := make([]SubCallback, 0)
	for _, item := range list {
		if &item != &cb {
			newList = append(newList, item)
		}
	}
	b.subscribers[topic] = newList
	logger.Logger.Info().Str("topic", topic).Str("handler", getFuncName(cb)).Msg("unsubscribe success")
}

func (b *PubSubBroker) Publish(topic string, msg any) {
	b.mu.RLock()
	list, ok := b.subscribers[topic]
	b.mu.RUnlock()
	msgID := MsgStore.SavePublish(topic, msg)
	logger.Logger.Info().Str("topic", topic).Str("msgID", msgID).Int("subCount", len(list)).Msg("publish message")
	if !ok || len(list) == 0 {
		logger.Logger.Warn().Str("topic", topic).Msg("no subscriber, only persist")
		return
	}
	allSuccess := true
	for _, cb := range list {
		func() {
			defer func() {
				if r := recover(); r != nil {
					allSuccess = false
					logger.Logger.Error().Any("panic", r).Str("handler", getFuncName(cb)).Str("topic", topic).Msg("sub callback panic")
				}
			}()
			cb(msg)
		}()
	}
	if allSuccess {
		MsgStore.MarkConsumed(msgID)
	}
}

func getFuncName(f SubCallback) string {
	return "callback"
}


/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:Publish/Subscribe Pattern 发布订阅模式
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : goLang 2024.3.6 go 26.2
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/6/24 20:33
# User      :  geovindu
# Product   : GoLand
# Project   : godesginpattern
# File      : exception.go
*/
package messaging

type PubSubException struct {
	Msg string
}

func (e PubSubException) Error() string {
	return e.Msg
}

type SubscriberInvalidError struct {
	PubSubException
}


/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:Publish/Subscribe Pattern 发布订阅模式
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : goLang 2024.3.6 go 26.2
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/6/24 20:34
# User      :  geovindu
# Product   : GoLand
# Project   : godesginpattern
# File      : message_store.go
*/
package messaging

import (
	"database/sql"
	"github.com/google/uuid"
	"godesginpattern/publishsubscribe/config"
	"godesginpattern/publishsubscribe/domain"
	"godesginpattern/publishsubscribe/logger"
	_ "modernc.org/sqlite"
	"time"
)

type MessageStore struct {
	dbPath string
	db     *sql.DB
}

var MsgStore = &MessageStore{dbPath: config.DBPath}

func (m *MessageStore) initDB() error {
	if m.db != nil {
		return nil
	}
	db, err := sql.Open("sqlite", m.dbPath)
	if err != nil {
		return err
	}
	db.SetMaxOpenConns(1)
	db.SetMaxIdleConns(1)
	m.db = db
	return nil
}

func (m *MessageStore) getConn() (*sql.DB, error) {
	if err := m.initDB(); err != nil {
		return nil, err
	}
	return m.db, nil
}

func (m *MessageStore) CreateTable() {
	db, err := m.getConn()
	if err != nil {
		logger.Logger.Error().Err(err).Msg("open db failed")
		return
	}
	sqlCreate := `CREATE TABLE IF NOT EXISTS message_record (
		msg_id TEXT PRIMARY KEY,
		topic TEXT NOT NULL,
		msg_data TEXT NOT NULL,
		publish_time TEXT NOT NULL,
		consume_status INTEGER DEFAULT 0
	)`
	_, err = db.Exec(sqlCreate)
	if err != nil {
		logger.Logger.Error().Err(err).Msg("create table failed")
	}
}

func (m *MessageStore) SavePublish(topic string, msg any) string {
	msgID := uuid.NewString()
	jsonStr := domain.ToJson(msg)
	now := time.Now().Format("2006-01-02 15:04:05")
	db, err := m.getConn()
	if err != nil {
		logger.Logger.Error().Err(err).Msg("save publish get conn err")
		return msgID
	}
	_, err = db.Exec(`INSERT INTO message_record(msg_id,topic,msg_data,publish_time) VALUES (?,?,?,?)`,
		msgID, topic, jsonStr, now)
	if err != nil {
		logger.Logger.Error().Err(err).Str("msgID", msgID).Msg("insert msg failed")
	} else {
		logger.Logger.Debug().Str("msgID", msgID).Str("topic", topic).Msg("message saved")
	}
	return msgID
}

func (m *MessageStore) MarkConsumed(msgID string) {
	db, err := m.getConn()
	if err != nil {
		logger.Logger.Error().Err(err).Msg("mark consumed get conn err")
		return
	}
	res, err := db.Exec(`UPDATE message_record SET consume_status=1 WHERE msg_id=?`, msgID)
	if err != nil {
		logger.Logger.Error().Err(err).Str("msgID", msgID).Msg("update consume status failed")
		return
	}
	rowCnt, _ := res.RowsAffected()
	if rowCnt > 0 {
		logger.Logger.Debug().Str("msgID", msgID).Msg("marked consumed")
	} else {
		logger.Logger.Warn().Str("msgID", msgID).Msg("no record to mark consumed")
	}
}


/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:Publish/Subscribe Pattern 发布订阅模式
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : goLang 2024.3.6 go 26.2
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/6/24 20:40
# User      :  geovindu
# Product   : GoLand
# Project   : godesginpattern
# File      : rabbit_broker.go
*/
package messaging

import (
	"encoding/json"
	"fmt"
	"github.com/rabbitmq/amqp091-go"
	"godesginpattern/publishsubscribe/config"
	"godesginpattern/publishsubscribe/logger"
	"sync"
)

type RabbitBroker struct {
	mu      sync.Mutex
	conn    *amqp091.Connection
	channel *amqp091.Channel
	subMap  map[string]bool
}

var RabbitBrokerIns = &RabbitBroker{
	subMap: make(map[string]bool),
}

func (r *RabbitBroker) connect() error {
	r.mu.Lock()
	defer r.mu.Unlock()
	if r.conn != nil && !r.conn.IsClosed() {
		return nil
	}
	url := fmt.Sprintf("amqp://%s:%s@%s:%d/%s",
		config.RabbitUser, config.RabbitPwd, config.RabbitHost, config.RabbitPort, config.RabbitVhost)
	conn, err := amqp091.Dial(url)
	if err != nil {
		return err
	}
	ch, err := conn.Channel()
	if err != nil {
		return err
	}
	r.conn = conn
	r.channel = ch
	logger.Logger.Info().Msg("rabbitmq connect success")
	return nil
}

func (r *RabbitBroker) Subscribe(topic string, cb SubCallback) error {
	if err := r.connect(); err != nil {
		return err
	}
	exName := topic
	_ = r.channel.ExchangeDeclare(exName, "fanout", true, false, false, false, nil)
	q, err := r.channel.QueueDeclare("", true, true, true, false, nil)
	if err != nil {
		return err
	}
	_ = r.channel.QueueBind(q.Name, "", exName, false, nil)
	msgs, err := r.channel.Consume(q.Name, "", false, false, false, false, nil)
	if err != nil {
		return err
	}
	go func() {
		for d := range msgs {
			var data map[string]any
			_ = json.Unmarshal(d.Body, &data)
			cb(data["data"])
			_ = d.Ack(false)
		}
	}()
	r.subMap[topic] = true
	logger.Logger.Info().Str("topic", topic).Str("queue", q.Name).Msg("rabbit subscribe ok")
	return nil
}

func (r *RabbitBroker) Publish(topic string, msg any) error {
	if err := r.connect(); err != nil {
		return err
	}
	exName := topic
	_ = r.channel.ExchangeDeclare(exName, "fanout", true, false, false, false, nil)
	msgID := MsgStore.SavePublish(topic, msg)
	payload := map[string]any{
		"msg_id": msgID,
		"data":   msg,
	}
	b, _ := json.Marshal(payload)
	err := r.channel.Publish(exName, "", false, false, amqp091.Publishing{
		Body: b,
	})
	if err != nil {
		logger.Logger.Error().Err(err).Str("topic", topic).Msg("rabbit publish failed")
		return err
	}
	logger.Logger.Info().Str("topic", topic).Str("msgID", msgID).Msg("rabbit publish ok")
	return nil
}

func (r *RabbitBroker) WrapPublish(topic string, msg any) {
	_ = r.Publish(topic, msg)
}

  

订阅者服务

/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:Publish/Subscribe Pattern 发布订阅模式
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : goLang 2024.3.6 go 26.2
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/6/24 20:46
# User      :  geovindu
# Product   : GoLand
# Project   : godesginpattern
# File      : customer.go
*/
package subscribers

import (
	"godesginpattern/publishsubscribe/domain"
	"godesginpattern/publishsubscribe/logger"
)

func CustomerHandle(msg any) {
	m, ok := msg.(domain.JewelryMessage)
	if !ok {
		logger.Logger.Info().Any("customer raw msg", msg)
		return
	}
	logger.Logger.Info().Str("dept", "👥客户部").Str("title", m.Title).Str("content", m.Content).Msg("receive msg")
}


/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:Publish/Subscribe Pattern 发布订阅模式
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : goLang 2024.3.6 go 26.2
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/6/24 20:44
# User      :  geovindu
# Product   : GoLand
# Project   : godesginpattern
# File      : finance.go
*/
package subscribers

import (
	"godesginpattern/publishsubscribe/domain"
	"godesginpattern/publishsubscribe/logger"
)

func FinanceHandle(msg any) {
	m, ok := msg.(domain.JewelryMessage)
	if !ok {
		logger.Logger.Info().Any("finance raw msg", msg)
		return
	}
	logger.Logger.Info().Str("dept", "💰财务部").Str("title", m.Title).Str("content", m.Content).Msg("receive msg")
}


/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:Publish/Subscribe Pattern 发布订阅模式
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : goLang 2024.3.6 go 26.2
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/6/24 20:45
# User      :  geovindu
# Product   : GoLand
# Project   : godesginpattern
# File      : quality.go
*/
package subscribers

import (
	"godesginpattern/publishsubscribe/domain"
	"godesginpattern/publishsubscribe/logger"
)

func QualityHandle(msg any) {
	m, ok := msg.(domain.JewelryMessage)
	if !ok {
		logger.Logger.Info().Any("quality raw msg", msg)
		return
	}
	logger.Logger.Info().Str("dept", "🔍质检部").Str("title", m.Title).Str("content", m.Content).Msg("receive msg")
}


/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:Publish/Subscribe Pattern 发布订阅模式
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : goLang 2024.3.6 go 26.2
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/6/24 20:45
# User      :  geovindu
# Product   : GoLand
# Project   : godesginpattern
# File      : sales.go
*/
package subscribers

import (
	"godesginpattern/publishsubscribe/domain"
	"godesginpattern/publishsubscribe/logger"
)

func SalesHandle(msg any) {
	m, ok := msg.(domain.JewelryMessage)
	if !ok {
		logger.Logger.Info().Any("sales raw msg", msg)
		return
	}
	logger.Logger.Info().Str("dept", "🛍️销售部").Str("title", m.Title).Str("content", m.Content).Msg("receive msg")
}


/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:Publish/Subscribe Pattern 发布订阅模式
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : goLang 2024.3.6 go 26.2
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/6/24 20:46
# User      :  geovindu
# Product   : GoLand
# Project   : godesginpattern
# File      : store.go
*/
package subscribers

import (
	"godesginpattern/publishsubscribe/domain"
	"godesginpattern/publishsubscribe/logger"
)

func StoreHandle(msg any) {
	m, ok := msg.(domain.JewelryMessage)
	if !ok {
		logger.Logger.Info().Any("store raw msg", msg)
		return
	}
	logger.Logger.Info().Str("dept", "🏪门店").Str("title", m.Title).Str("content", m.Content).Msg("receive msg")
}


/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:Publish/Subscribe Pattern 发布订阅模式
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : goLang 2024.3.6 go 26.2
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/6/24 20:47
# User      :  geovindu
# Product   : GoLand
# Project   : godesginpattern
# File      : tech.go
*/
package subscribers

import (
	"godesginpattern/publishsubscribe/domain"
	"godesginpattern/publishsubscribe/logger"
)

func TechHandle(msg any) {
	m, ok := msg.(domain.JewelryMessage)
	if !ok {
		logger.Logger.Info().Any("tech raw msg", msg)
		return
	}
	logger.Logger.Info().Str("dept", "🔧技术部").Str("title", m.Title).Str("content", m.Content).Msg("receive msg")
}


/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:Publish/Subscribe Pattern 发布订阅模式
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : goLang 2024.3.6 go 26.2
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/6/24 20:43
# User      :  geovindu
# Product   : GoLand
# Project   : godesginpattern
# File      : warehouse.go
*/
package subscribers

import (
	"godesginpattern/publishsubscribe/domain"
	"godesginpattern/publishsubscribe/logger"
)

func WarehouseHandle(msg any) {
	m, ok := msg.(domain.JewelryMessage)
	if !ok {
		logger.Logger.Info().Any("warehouse raw msg", msg)
		return
	}
	logger.Logger.Info().Str("dept", "🏬仓库部").Str("title", m.Title).Str("content", m.Content).Msg("receive msg")
}

  

发布者服务

/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:Publish/Subscribe Pattern 发布订阅模式
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : goLang 2024.3.6 go 26.2
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/6/24 20:50
# User      :  geovindu
# Product   : GoLand
# Project   : godesginpattern
# File      : after_sales.go
*/
package publishers

import (
	"fmt"
	"godesginpattern/publishsubscribe/config"
	"godesginpattern/publishsubscribe/domain"
	"godesginpattern/publishsubscribe/messaging"
)

func AfterSalesPublish(customerName, serviceType string) {
	msg := domain.AfterSalesCareMsg{
		JewelryMessage: domain.JewelryMessage{
			Title:      "客户售后保养申请",
			Content:    fmt.Sprintf("客户%s申请%s服务,已受理", customerName, serviceType),
			Department: "客服部",
		},
		CustomerName: customerName,
		ServiceType:  serviceType,
	}
	pubFunc := getBroker()
	messaging.AsyncPub.PublishAsync(pubFunc, config.TopicConst.AfterSalesCare, msg)
}


/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:Publish/Subscribe Pattern 发布订阅模式
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : goLang 2024.3.6 go 26.2
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/6/24 20:49
# User      :  geovindu
# Product   : GoLand
# Project   : godesginpattern
# File      : distribution.go
*/
package publishers

import (
	"fmt"
	"godesginpattern/publishsubscribe/config"
	"godesginpattern/publishsubscribe/domain"
	"godesginpattern/publishsubscribe/messaging"
)

func DistributionPublish(storeName string, distributeCount int) {
	msg := domain.StoreDistributionMsg{
		JewelryMessage: domain.JewelryMessage{
			Title:      "门店铺货完成",
			Content:    fmt.Sprintf("向%s铺货%d件珠宝,已出库发货", storeName, distributeCount),
			Department: "仓库部",
		},
		StoreName:       storeName,
		DistributeCount: distributeCount,
	}
	pubFunc := getBroker()
	messaging.AsyncPub.PublishAsync(pubFunc, config.TopicConst.StoreDistribution, msg)
}


/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:Publish/Subscribe Pattern 发布订阅模式
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : goLang 2024.3.6 go 26.2
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/6/24 20:49
# User      :  geovindu
# Product   : GoLand
# Project   : godesginpattern
# File      : inspection.go
*/
package publishers

import (
	"fmt"
	"godesginpattern/publishsubscribe/config"
	"godesginpattern/publishsubscribe/domain"
	"godesginpattern/publishsubscribe/messaging"
)

func InspectionPublish(qualifiedCount int, hasCertificate bool) {
	certStr := "未出具证书"
	if hasCertificate {
		certStr = "已出具证书"
	}
	msg := domain.QualityInspectionMsg{
		JewelryMessage: domain.JewelryMessage{
			Title:      "珠宝质量检测完成",
			Content:    fmt.Sprintf("总计%d件珠宝,全部合格,%s", qualifiedCount, certStr),
			Department: "质检部",
		},
		QualifiedCount: qualifiedCount,
		HasCertificate: hasCertificate,
	}
	pubFunc := getBroker()
	messaging.AsyncPub.PublishAsync(pubFunc, config.TopicConst.QualityInspection, msg)
}

/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:Publish/Subscribe Pattern 发布订阅模式
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : goLang 2024.3.6 go 26.2
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/6/24 20:48
# User      :  geovindu
# Product   : GoLand
# Project   : godesginpattern
# File      : production.go
*/
package publishers

import (
	"fmt"
	"godesginpattern/publishsubscribe/config"
	"godesginpattern/publishsubscribe/domain"
	"godesginpattern/publishsubscribe/messaging"
)

func ProductionPublish(productType string, count int) {
	msg := domain.JewelryProductionMsg{
		JewelryMessage: domain.JewelryMessage{
			Title:      "珠宝生产完成",
			Content:    productType + fmt.Sprintf("%d件,待质检", count),
			Department: "生产部",
		},
		ProductCount: count,
		ProductType:  productType,
	}
	pubFunc := getBroker()
	messaging.AsyncPub.PublishAsync(pubFunc, config.TopicConst.JewelryProduction, msg)
}


/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:Publish/Subscribe Pattern 发布订阅模式
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : goLang 2024.3.6 go 26.2
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/6/24 20:48
# User      :  geovindu
# Product   : GoLand
# Project   : godesginpattern
# File      : purchase.go
*/
package publishers

import (
	"godesginpattern/publishsubscribe/config"
	"godesginpattern/publishsubscribe/domain"
	"godesginpattern/publishsubscribe/messaging"
)

func getBroker() messaging.PublishFunc {
	if config.UseRabbitMQ {
		// 使用包装后的无返回方法
		return messaging.RabbitBrokerIns.WrapPublish
	}
	// 本地Broker原生无返回,直接返回
	return messaging.LocalBroker.Publish
}

func PurchasePublish(material, quantity string) {
	msg := domain.RawMaterialPurchaseMsg{
		JewelryMessage: domain.JewelryMessage{
			Title:      "原材料采购完成",
			Content:    material + " " + quantity + ",待入库",
			Department: "采购部",
		},
		Material: material,
		Quantity: quantity,
	}
	pubFunc := getBroker()
	messaging.AsyncPub.PublishAsync(pubFunc, config.TopicConst.RawMaterialPurchase, msg)
}

/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:Publish/Subscribe Pattern 发布订阅模式
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : goLang 2024.3.6 go 26.2
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/6/24 20:50
# User      :  geovindu
# Product   : GoLand
# Project   : godesginpattern
# File      : sale.go
*/
package publishers

import (
	"fmt"
	"godesginpattern/publishsubscribe/config"
	"godesginpattern/publishsubscribe/domain"
	"godesginpattern/publishsubscribe/messaging"
)

func SalePublish(productName string, amount float64, store string) {
	msg := domain.JewelrySaleMsg{
		JewelryMessage: domain.JewelryMessage{
			Title:      "珠宝销售成功",
			Content:    fmt.Sprintf("%s售出%s,收款%.2f元,库存已扣减", store, productName, amount),
			Department: "门店",
		},
		ProductName: productName,
		Amount:      amount,
		Store:       store,
	}
	pubFunc := getBroker()
	messaging.AsyncPub.PublishAsync(pubFunc, config.TopicConst.JewelrySale, msg)
}

  

调用:

/*
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:Publish/Subscribe Pattern 发布订阅模式
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : goLang 2024.3.6 go 26.2
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/6/24 20:51
# User      :  geovindu
# Product   : GoLand
# Project   : godesginpattern
# File      : publishsubscribebll.go
*/
package bll

import (
	"godesginpattern/publishsubscribe/config"
	"godesginpattern/publishsubscribe/logger"
	"godesginpattern/publishsubscribe/messaging"
	"godesginpattern/publishsubscribe/service/publishers"
	"godesginpattern/publishsubscribe/service/subscribers"
)

func registerSubscribers() {
	t := config.TopicConst
	b := messaging.LocalBroker
	if config.UseRabbitMQ {
		panic("本地测试请将UseRabbitMQ置false")
	}
	b.Subscribe(t.RawMaterialPurchase, subscribers.WarehouseHandle)
	b.Subscribe(t.RawMaterialPurchase, subscribers.FinanceHandle)

	b.Subscribe(t.JewelryProduction, subscribers.QualityHandle)
	b.Subscribe(t.JewelryProduction, subscribers.WarehouseHandle)

	b.Subscribe(t.QualityInspection, subscribers.SalesHandle)
	b.Subscribe(t.QualityInspection, subscribers.WarehouseHandle)
	b.Subscribe(t.QualityInspection, subscribers.FinanceHandle)

	b.Subscribe(t.StoreDistribution, subscribers.StoreHandle)
	b.Subscribe(t.StoreDistribution, subscribers.SalesHandle)

	b.Subscribe(t.JewelrySale, subscribers.FinanceHandle)
	b.Subscribe(t.JewelrySale, subscribers.WarehouseHandle)

	b.Subscribe(t.AfterSalesCare, subscribers.CustomerHandle)
	b.Subscribe(t.AfterSalesCare, subscribers.TechHandle)
}

func PublishSubscribeMain() {
	// 初始化配置、日志、数据库表
	config.InitConfig()
	logger.InitLogger()
	messaging.MsgStore.CreateTable()
	logger.Logger.Info().Msg("===== Go珠宝企业PubSub系统启动 =====")

	registerSubscribers()

	// 完整业务流程
	publishers.PurchasePublish("18K金+天然钻石", "500克+100颗")
	publishers.ProductionPublish("钻石戒指、黄金项链", 80)
	publishers.InspectionPublish(80, true)
	publishers.DistributionPublish("北京朝阳店、上海南京路店", 80)
	publishers.SalePublish("经典六爪钻戒", 12800.0, "上海南京路店")
	publishers.AfterSalesPublish("张先生", "钻石清洗、抛光保养")

	// 取消订阅测试
	logger.Logger.Info().Msg("执行取消仓库【珠宝销售】主题订阅")
	messaging.LocalBroker.UnSubscribe(config.TopicConst.JewelrySale, subscribers.WarehouseHandle)
	publishers.SalePublish("足金素圈项链", 3800.0, "北京朝阳店")

	// 等待异步任务执行完成
	messaging.AsyncPub.WaitAndClose()
	logger.Logger.Info().Msg("===== Go珠宝企业PubSub系统运行结束 =====")
}

  

输出:

image

 

image

 

 

posted @ 2026-06-24 22:59  ®Geovin Du Dream Park™  阅读(2)  评论(0)    收藏  举报