项目结构:

Go
/*
# 版权所有 2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search
# 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/7/26 22:08
# User : geovindu
# Product : GoLand
# Project : goalgorithms
# File : app_config.go
*/
package config
import "os"
// AppConfig 全局应用配置
type AppConfig struct {
LogSavePath string // 日志文件夹路径
LogFileName string // 日志文件名
LogConsoleEnable bool
LogFileEnable bool
MaxWorkerThread int // 协程池最大并发数量
MaxSearchState int // 搜索最大状态上限,防止爆炸
}
var GlobalConfig = &AppConfig{
LogSavePath: "./logs",
LogFileName: "jewelry_packer.log",
LogConsoleEnable: true,
LogFileEnable: true,
MaxWorkerThread: 8,
MaxSearchState: 25000,
}
// InitLogDir 创建日志目录
func InitLogDir() error {
if _, err := os.Stat(GlobalConfig.LogSavePath); os.IsNotExist(err) {
return os.MkdirAll(GlobalConfig.LogSavePath, 0755)
}
return nil
}
/*
# 版权所有 2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search
# 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/7/26 22:09
# User : geovindu
# Product : GoLand
# Project : goalgorithms
# File : enums.go
*/
package core
// LoadStrategyType 配载算法策略类型
type LoadStrategyType string
const (
StrategyBFS LoadStrategyType = "BFS"
StrategyDFS LoadStrategyType = "DFS"
StrategyAStar LoadStrategyType = "ASTAR"
)
// ResultCode 返回业务码
type ResultCode struct {
Code int
Msg string
}
var (
CodeSuccess = ResultCode{0, "成功"}
CodeParamError = ResultCode{400, "参数非法"}
CodeNoAvailablePack = ResultCode{401, "无可用包装规格"}
CodeConstraintFail = ResultCode{402, "装箱约束冲突,无法装载"}
CodeSearchOverLimit = ResultCode{501, "搜索状态超限,未找到方案"}
CodeNoAnyLoadPlan = ResultCode{502, "无法生成配载方案"}
)
/*
# 版权所有 2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search
# 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/7/26 22:09
# User : geovindu
# Product : GoLand
# Project : goalgorithms
# File : models.go
*/
package core
// JewelryItem 单类首饰明细
type JewelryItem struct {
JewelryCode string // 首饰货号
SingleVolume float64 // 单件体积 cm³
Quantity int // 品类数量
}
// GetTotalVolume 获取该品类首饰合计总体积
func (j *JewelryItem) GetTotalVolume() float64 {
return j.SingleVolume * float64(j.Quantity)
}
// PackConstraint 装箱全局约束规则
type PackConstraint struct {
BufferCoeff float64 // 缓冲空隙系数,如0.85
SingleBoxMaxQty int // 单个礼盒最多放置首饰件数
ForbidMixJewelry bool // true=禁止不同品类混装
ForbidSplitJewelry bool // true=禁止同一品类拆分多个盒子
}
// PackageSpec 包装规格实体
type PackageSpec struct {
SpecCode string // 包装编码
Volume float64 // 标称容积 cm³
StockQty int // 可用库存
UnitCost float64 // 单个包装成本
PriorityWeight int // 优先级权重,数值越大优先选用
}
// GetValidVolume 根据缓冲系数计算有效装载容积
func (p *PackageSpec) GetValidVolume(coeff float64) float64 {
return p.Volume * coeff
}
// PackQueryCondition 配载查询入参
type PackQueryCondition struct {
JewelryList []JewelryItem
Constraint *PackConstraint
TotalMaterialVol float64
PackageSpecList []PackageSpec
StrategyType LoadStrategyType
AllowOverLoad bool
}
// SingleUsePackage 方案内使用的包装记录
type SingleUsePackage struct {
SpecCode string
Volume float64
ValidVolume float64
UseCount int
BelongJewelryCode string
}
// PackResult 配载最终输出结果
type PackResult struct {
Code int
Msg string
UsePackageList []SingleUsePackage
RemainMaterialVol float64
RemainPackageStock map[string]int
TotalUsePackageCnt int
SearchStateCount int
FullLoadFlag bool
JewelryTotalCalcVol float64
TotalCost float64 // 方案总成本
}
/*
# 版权所有 2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search
# 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/7/26 22:10
# User : geovindu
# Product : GoLand
# Project : goalgorithms
# File : logger.go
*/
package core
import (
"github.com/rs/zerolog"
"goalgorithms/breadthfirst/config"
"io"
"os"
"sync"
)
var (
loggerInstance *zerolog.Logger
loggerOnce sync.Once
)
// GetLogger 获取全局日志单例,协程安全
func GetLogger() *zerolog.Logger {
loggerOnce.Do(func() {
_ = config.InitLogDir()
var writers []io.Writer
// 控制台输出
if config.GlobalConfig.LogConsoleEnable {
consoleWriter := zerolog.ConsoleWriter{
Out: os.Stdout,
TimeFormat: "2006-01-02 15:04:05",
}
writers = append(writers, consoleWriter)
}
// 文件输出
if config.GlobalConfig.LogFileEnable {
logPath := config.GlobalConfig.LogSavePath + "/" + config.GlobalConfig.LogFileName
f, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err == nil {
writers = append(writers, f)
}
}
multi := io.MultiWriter(writers...)
l := zerolog.New(multi).With().Timestamp().Logger()
loggerInstance = &l
})
return loggerInstance
}
/*
# 版权所有 2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search
# 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/7/26 22:11
# User : geovindu
# Product : GoLand
# Project : goalgorithms
# File : strategy_base.go
*/
package core
// PackStrategy 配载策略接口
type PackStrategy interface {
Execute(condition *PackQueryCondition) *PackResult
}
/*
# 版权所有 2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search
# 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/7/26 22:11
# User : geovindu
# Product : GoLand
# Project : goalgorithms
# File : strategy_bfs.go
*/
package core
import (
"container/list"
"fmt"
"goalgorithms/breadthfirst/config"
"math"
)
// BFSPackStrategy BFS广度优先搜索
// 择优优先级:最少包装盒数量 > 权重总和更高 > 总成本更低
type BFSPackStrategy struct{}
func NewBFSPackStrategy() PackStrategy {
return &BFSPackStrategy{}
}
type bfsNode struct {
loadVol float64
usedMap map[string]int
totalCost float64
weightSum int
packCount int
}
func (b *BFSPackStrategy) Execute(condition *PackQueryCondition) *PackResult {
logger := GetLogger()
totalMatVol := condition.TotalMaterialVol
specList := condition.PackageSpecList
allowOver := condition.AllowOverLoad
maxState := config.GlobalConfig.MaxSearchState
if totalMatVol <= 0 {
return &PackResult{
Code: CodeParamError.Code,
Msg: CodeParamError.Msg,
RemainMaterialVol: totalMatVol,
RemainPackageStock: map[string]int{},
}
}
if len(specList) == 0 {
return &PackResult{
Code: CodeNoAvailablePack.Code,
Msg: CodeNoAvailablePack.Msg,
RemainMaterialVol: totalMatVol,
RemainPackageStock: map[string]int{},
}
}
initStock := make(map[string]int)
specVolMap := make(map[string]float64)
specCostMap := make(map[string]float64)
specWeightMap := make(map[string]int)
for _, item := range specList {
initStock[item.SpecCode] = item.StockQty
specVolMap[item.SpecCode] = item.Volume
specCostMap[item.SpecCode] = item.UnitCost
specWeightMap[item.SpecCode] = item.PriorityWeight
}
// 初始化
startUsed := make(map[string]int)
for k := range initStock {
startUsed[k] = 0
}
visited := make(map[string]bool)
visitedCount := 1
queue := list.New()
startNode := bfsNode{
loadVol: 0,
usedMap: copyMap(startUsed),
totalCost: 0,
weightSum: 0,
packCount: 0,
}
queue.PushBack(startNode)
visited[genStateKey(startUsed)] = true
var bestNode *bfsNode
var minPackCnt *int
for queue.Len() > 0 {
elem := queue.Front()
queue.Remove(elem)
node := elem.Value.(bfsNode)
if node.loadVol >= totalMatVol {
if bestNode == nil {
bestNode = &node
minPackCnt = &node.packCount
} else {
if node.packCount == *minPackCnt {
if node.weightSum > bestNode.weightSum ||
(node.weightSum == bestNode.weightSum && node.totalCost < bestNode.totalCost) {
bestNode = &node
}
}
}
continue
}
if visitedCount >= maxState {
logger.Warn().Int("maxState", maxState).Msg("搜索达到最大状态限制,终止搜索")
break
}
for specCode, stockNum := range initStock {
usedNum := node.usedMap[specCode]
if usedNum >= stockNum {
continue
}
packVol := specVolMap[specCode]
newLoad := node.loadVol + packVol
if !allowOver && newLoad > totalMatVol {
if node.loadVol == 0 && packVol >= totalMatVol {
} else {
continue
}
}
newUsed := copyMap(node.usedMap)
newUsed[specCode]++
key := genStateKey(newUsed)
if visited[key] {
continue
}
visited[key] = true
visitedCount++
newNode := bfsNode{
loadVol: newLoad,
usedMap: newUsed,
totalCost: node.totalCost + specCostMap[specCode],
weightSum: node.weightSum + specWeightMap[specCode],
packCount: node.packCount + 1,
}
queue.PushBack(newNode)
}
}
if bestNode == nil {
return &PackResult{
Code: CodeNoAnyLoadPlan.Code,
Msg: CodeNoAnyLoadPlan.Msg,
RemainMaterialVol: totalMatVol,
RemainPackageStock: initStock,
SearchStateCount: visitedCount,
}
}
remainMat := math.Max(0, totalMatVol-bestNode.loadVol)
fullFlag := bestNode.loadVol >= totalMatVol
useList := make([]SingleUsePackage, 0)
remainStock := make(map[string]int)
jewelryCode := ""
if len(condition.JewelryList) > 0 {
jewelryCode = condition.JewelryList[0].JewelryCode
}
for code, totalStock := range initStock {
useCnt := bestNode.usedMap[code]
remainStock[code] = totalStock - useCnt
if useCnt > 0 {
useList = append(useList, SingleUsePackage{
SpecCode: code,
Volume: specVolMap[code],
ValidVolume: specVolMap[code],
UseCount: useCnt,
BelongJewelryCode: jewelryCode,
})
}
}
return &PackResult{
Code: CodeSuccess.Code,
Msg: CodeSuccess.Msg,
UsePackageList: useList,
RemainMaterialVol: remainMat,
RemainPackageStock: remainStock,
TotalUsePackageCnt: bestNode.packCount,
SearchStateCount: visitedCount,
FullLoadFlag: fullFlag,
TotalCost: bestNode.totalCost,
}
}
func copyMap(m map[string]int) map[string]int {
n := make(map[string]int, len(m))
for k, v := range m {
n[k] = v
}
return n
}
func genStateKey(m map[string]int) string {
var s string
for k, v := range m {
s += fmt.Sprintf("%s:%d,", k, v)
}
return s
}
/*
# 版权所有 2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search
# 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/7/26 22:12
# User : geovindu
# Product : GoLand
# Project : goalgorithms
# File : strategy_dfs.go
*/
package core
import (
"goalgorithms/breadthfirst/config"
"math"
)
type DFSPackStrategy struct {
bestNode *dfsNode
visited map[string]bool
visitedCnt int
maxState int
totalMatVol float64
allowOver bool
specVolMap map[string]float64
specCostMap map[string]float64
initStock map[string]int
}
type dfsNode struct {
loadVol float64
usedMap map[string]int
totalCost float64
packCnt int
}
func NewDFSPackStrategy() PackStrategy {
return &DFSPackStrategy{}
}
func (d *DFSPackStrategy) Execute(condition *PackQueryCondition) *PackResult {
d.totalMatVol = condition.TotalMaterialVol
d.allowOver = condition.AllowOverLoad
d.maxState = config.GlobalConfig.MaxSearchState
specList := condition.PackageSpecList
if d.totalMatVol <= 0 {
return &PackResult{Code: CodeParamError.Code, Msg: CodeParamError.Msg}
}
if len(specList) == 0 {
return &PackResult{Code: CodeNoAvailablePack.Code, Msg: CodeNoAvailablePack.Msg}
}
d.initStock = make(map[string]int)
d.specVolMap = make(map[string]float64)
d.specCostMap = make(map[string]float64)
for _, item := range specList {
d.initStock[item.SpecCode] = item.StockQty
d.specVolMap[item.SpecCode] = item.Volume
d.specCostMap[item.SpecCode] = item.UnitCost
}
startUsed := make(map[string]int)
for k := range d.initStock {
startUsed[k] = 0
}
d.visited = make(map[string]bool)
d.visitedCnt = 0
d.bestNode = nil
d.dfs(0, startUsed, 0, 0)
if d.bestNode == nil {
return &PackResult{
Code: CodeNoAnyLoadPlan.Code,
Msg: CodeNoAnyLoadPlan.Msg,
RemainMaterialVol: d.totalMatVol,
RemainPackageStock: copyMap(d.initStock),
SearchStateCount: d.visitedCnt,
}
}
remainMat := math.Max(0, d.totalMatVol-d.bestNode.loadVol)
fullFlag := d.bestNode.loadVol >= d.totalMatVol
useList := make([]SingleUsePackage, 0)
remainStock := make(map[string]int)
jewelryCode := ""
if len(condition.JewelryList) > 0 {
jewelryCode = condition.JewelryList[0].JewelryCode
}
for code, totalStock := range d.initStock {
useCnt := d.bestNode.usedMap[code]
remainStock[code] = totalStock - useCnt
if useCnt > 0 {
useList = append(useList, SingleUsePackage{
SpecCode: code,
Volume: d.specVolMap[code],
ValidVolume: d.specVolMap[code],
UseCount: useCnt,
BelongJewelryCode: jewelryCode,
})
}
}
return &PackResult{
Code: CodeSuccess.Code,
Msg: CodeSuccess.Msg,
UsePackageList: useList,
RemainMaterialVol: remainMat,
RemainPackageStock: remainStock,
TotalUsePackageCnt: d.bestNode.packCnt,
SearchStateCount: d.visitedCnt,
FullLoadFlag: fullFlag,
TotalCost: d.bestNode.totalCost,
}
}
func (d *DFSPackStrategy) dfs(loadVol float64, usedMap map[string]int, cost float64, packCnt int) {
if d.bestNode != nil {
return
}
if loadVol >= d.totalMatVol {
d.bestNode = &dfsNode{
loadVol: loadVol,
usedMap: copyMap(usedMap),
totalCost: cost,
packCnt: packCnt,
}
return
}
key := genStateKey(usedMap)
if d.visited[key] {
return
}
if d.visitedCnt >= d.maxState {
return
}
d.visited[key] = true
d.visitedCnt++
for specCode, stockNum := range d.initStock {
usedNum := usedMap[specCode]
if usedNum >= stockNum {
continue
}
pv := d.specVolMap[specCode]
newLoad := loadVol + pv
if !d.allowOver && newLoad > d.totalMatVol {
if loadVol == 0 && pv >= d.totalMatVol {
} else {
continue
}
}
usedMap[specCode]++
d.dfs(newLoad, usedMap, cost+d.specCostMap[specCode], packCnt+1)
usedMap[specCode]--
}
}
/*
# 版权所有 2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search
# 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/7/26 22:13
# User : geovindu
# Product : GoLand
# Project : goalgorithms
# File : strategy_astar.go
*/
package core
import (
"container/heap"
"goalgorithms/breadthfirst/config"
"math"
)
type AStarPackStrategy struct{}
func NewAStarPackStrategy() PackStrategy {
return &AStarPackStrategy{}
}
type astarItem struct {
f float64
g int
loadVol float64
cost float64
usedMap map[string]int
}
type priorityQueue []*astarItem
func (pq priorityQueue) Len() int { return len(pq) }
func (pq priorityQueue) Less(i, j int) bool { return pq[i].f < pq[j].f }
func (pq priorityQueue) Swap(i, j int) { pq[i], pq[j] = pq[j], pq[i] }
func (pq *priorityQueue) Push(x interface{}) { *pq = append(*pq, x.(*astarItem)) }
func (pq *priorityQueue) Pop() interface{} {
old := *pq
n := len(old)
item := old[n-1]
*pq = old[:n-1]
return item
}
func (a *AStarPackStrategy) Execute(condition *PackQueryCondition) *PackResult {
logger := GetLogger()
totalMatVol := condition.TotalMaterialVol
specList := condition.PackageSpecList
allowOver := condition.AllowOverLoad
maxState := config.GlobalConfig.MaxSearchState
if totalMatVol <= 0 {
return &PackResult{Code: CodeParamError.Code, Msg: CodeParamError.Msg}
}
if len(specList) == 0 {
return &PackResult{Code: CodeNoAvailablePack.Code, Msg: CodeNoAvailablePack.Msg}
}
initStock := make(map[string]int)
specVolMap := make(map[string]float64)
specCostMap := make(map[string]float64)
specWeightMap := make(map[string]int)
for _, item := range specList {
initStock[item.SpecCode] = item.StockQty
specVolMap[item.SpecCode] = item.Volume
specCostMap[item.SpecCode] = item.UnitCost
specWeightMap[item.SpecCode] = item.PriorityWeight
}
startUsed := make(map[string]int)
for k := range initStock {
startUsed[k] = 0
}
visited := make(map[string]bool)
visitedCount := 1
pq := make(priorityQueue, 0)
heap.Init(&pq)
heap.Push(&pq, &astarItem{f: 0, g: 0, loadVol: 0, cost: 0, usedMap: copyMap(startUsed)})
var best *astarItem
for pq.Len() > 0 {
top := heap.Pop(&pq).(*astarItem)
key := genStateKey(top.usedMap)
if visited[key] {
continue
}
visited[key] = true
visitedCount++
if top.loadVol >= totalMatVol {
best = top
break
}
if visitedCount >= maxState {
logger.Warn().Msg("A*搜索达到最大状态限制")
break
}
for specCode, stockNum := range initStock {
usedNum := top.usedMap[specCode]
if usedNum >= stockNum {
continue
}
pv := specVolMap[specCode]
newLoad := top.loadVol + pv
if !allowOver && newLoad > totalMatVol {
if top.loadVol == 0 && pv >= totalMatVol {
} else {
continue
}
}
newG := top.g + 1
newCost := top.cost + specCostMap[specCode]
h := maxFloat(totalMatVol-newLoad, 0)*0.01 + newCost*0.15 - float64(specWeightMap[specCode])*0.005
newF := float64(newG) + h
newUsed := copyMap(top.usedMap)
newUsed[specCode]++
heap.Push(&pq, &astarItem{
f: newF,
g: newG,
loadVol: newLoad,
cost: newCost,
usedMap: newUsed,
})
}
}
if best == nil {
return &PackResult{
Code: CodeNoAnyLoadPlan.Code,
Msg: CodeNoAnyLoadPlan.Msg,
RemainMaterialVol: totalMatVol,
RemainPackageStock: copyMap(initStock),
SearchStateCount: visitedCount,
}
}
remainMat := math.Max(0, totalMatVol-best.loadVol)
fullFlag := best.loadVol >= totalMatVol
useList := make([]SingleUsePackage, 0)
remainStock := make(map[string]int)
jewelryCode := ""
if len(condition.JewelryList) > 0 {
jewelryCode = condition.JewelryList[0].JewelryCode
}
for code, totalStock := range initStock {
useCnt := best.usedMap[code]
remainStock[code] = totalStock - useCnt
if useCnt > 0 {
useList = append(useList, SingleUsePackage{
SpecCode: code,
Volume: specVolMap[code],
ValidVolume: specVolMap[code],
UseCount: useCnt,
BelongJewelryCode: jewelryCode,
})
}
}
return &PackResult{
Code: CodeSuccess.Code,
Msg: CodeSuccess.Msg,
UsePackageList: useList,
RemainMaterialVol: remainMat,
RemainPackageStock: remainStock,
TotalUsePackageCnt: best.g,
SearchStateCount: visitedCount,
FullLoadFlag: fullFlag,
TotalCost: best.cost,
}
}
func maxFloat(a, b float64) float64 {
if a > b {
return a
}
return b
}
/*
# 版权所有 2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search
# 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/7/26 22:15
# User : geovindu
# Product : GoLand
# Project : goalgorithms
# File : thread_pool.go
*/
package concurrency
import (
"goalgorithms/breadthfirst/config"
"goalgorithms/breadthfirst/core"
"sync"
"time"
)
type taskFunc func()
// SafePool 协程池单例
type SafePool struct {
wg sync.WaitGroup
taskC chan taskFunc
}
var poolOnce sync.Once
var globalPool *SafePool
func GetPool() *SafePool {
poolOnce.Do(func() {
maxWorker := config.GlobalConfig.MaxWorkerThread
p := &SafePool{
taskC: make(chan taskFunc, maxWorker*2),
}
for i := 0; i < maxWorker; i++ {
go func() {
for fn := range p.taskC {
fn()
}
}()
}
globalPool = p
})
return globalPool
}
// Submit 提交单个任务同步等待
func (p *SafePool) Submit(strategy core.PackStrategy, cond *core.PackQueryCondition) *core.PackResult {
resChan := make(chan *core.PackResult, 1)
p.taskC <- func() {
resChan <- strategy.Execute(cond)
}
select {
case r := <-resChan:
return r
case <-time.After(30 * time.Second):
return &core.PackResult{
Code: core.CodeSearchOverLimit.Code,
Msg: "任务执行超时",
}
}
}
/*
# 版权所有 2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search
# 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/7/26 22:15
# User : geovindu
# Product : GoLand
# Project : goalgorithms
# File : package_service.go
*/
package service
import (
"fmt"
"strings"
"github.com/rs/zerolog"
"goalgorithms/breadthfirst/concurrency"
"goalgorithms/breadthfirst/core"
)
type JewelryPackageService struct {
logger *zerolog.Logger
}
func NewService() *JewelryPackageService {
return &JewelryPackageService{
logger: core.GetLogger(),
}
}
// CalcJewelryTotalVolume 汇总所有首饰总体积
func (s *JewelryPackageService) CalcJewelryTotalVolume(list []core.JewelryItem) float64 {
var total float64
s.logger.Info().Msg("=====开始计算各类首饰物料体积明细=====")
for _, item := range list {
vol := item.GetTotalVolume()
s.logger.Info().
Str("货号", item.JewelryCode).
Float64("单件体积", item.SingleVolume).
Int("数量", item.Quantity).
Float64("品类合计", vol).Msg("")
total += vol
}
s.logger.Info().Float64("理论总体积", total).Msg("【汇总完成】")
return total
}
// CheckForbidSplitConstraint 禁止拆盒前置校验
func (s *JewelryPackageService) CheckForbidSplitConstraint(item *core.JewelryItem, packages []core.PackageSpec, coeff float64, maxQty int) bool {
if maxQty > 0 && item.Quantity > maxQty {
s.logger.Warn().
Str("jewelryCode", item.JewelryCode).
Int("qty", item.Quantity).
Int("maxQty", maxQty).
Msg("约束调整:单盒最大件数限制小于品类数量,自动允许拆分该品类")
return true
}
totalV := item.GetTotalVolume()
for _, pkg := range packages {
valid := pkg.GetValidVolume(coeff)
if totalV <= valid {
return true
}
}
s.logger.Error().
Str("jewelryCode", item.JewelryCode).
Float64("needVol", totalV).
Msg("约束冲突:禁止拆盒,但无盒子完整容纳该品类")
return false
}
// SplitByJewelry 禁止混装时拆分多个独立任务
func (s *JewelryPackageService) SplitByJewelry(origin *core.PackQueryCondition) []*core.PackQueryCondition {
cons := origin.Constraint
if cons == nil || !cons.ForbidMixJewelry {
return []*core.PackQueryCondition{origin}
}
s.logger.Info().Msg("【启用禁止混装规则】各品类独立配载")
var subList []*core.PackQueryCondition
for _, j := range origin.JewelryList {
if cons.ForbidSplitJewelry && cons.SingleBoxMaxQty > 0 && j.Quantity > cons.SingleBoxMaxQty {
s.logger.Warn().
Str("jewelryCode", j.JewelryCode).
Int("qty", j.Quantity).
Int("maxQty", cons.SingleBoxMaxQty).
Msg("自动拆分品类:禁止拆盒与单盒最大件数冲突,按最大件数拆分")
numGroups := (j.Quantity + cons.SingleBoxMaxQty - 1) / cons.SingleBoxMaxQty
for i := 0; i < numGroups; i++ {
start := i * cons.SingleBoxMaxQty
end := start + cons.SingleBoxMaxQty
if end > j.Quantity {
end = j.Quantity
}
subQty := end - start
subItem := core.JewelryItem{
JewelryCode: j.JewelryCode + fmt.Sprintf("_%d", i+1),
SingleVolume: j.SingleVolume,
Quantity: subQty,
}
subVol := subItem.GetTotalVolume()
subCond := &core.PackQueryCondition{
JewelryList: []core.JewelryItem{subItem},
Constraint: cons,
TotalMaterialVol: subVol,
PackageSpecList: origin.PackageSpecList,
StrategyType: origin.StrategyType,
AllowOverLoad: origin.AllowOverLoad,
}
subList = append(subList, subCond)
}
continue
}
if cons.ForbidSplitJewelry {
if !s.CheckForbidSplitConstraint(&j, origin.PackageSpecList, cons.BufferCoeff, cons.SingleBoxMaxQty) {
continue
}
}
subItem := j
subVol := j.GetTotalVolume()
subCond := &core.PackQueryCondition{
JewelryList: []core.JewelryItem{subItem},
Constraint: cons,
TotalMaterialVol: subVol,
PackageSpecList: origin.PackageSpecList,
StrategyType: origin.StrategyType,
AllowOverLoad: origin.AllowOverLoad,
}
subList = append(subList, subCond)
}
return subList
}
func (s *JewelryPackageService) CreateStrategy(t core.LoadStrategyType) core.PackStrategy {
switch t {
case core.StrategyBFS:
return core.NewBFSPackStrategy()
case core.StrategyDFS:
return core.NewDFSPackStrategy()
case core.StrategyAStar:
return core.NewAStarPackStrategy()
default:
panic(fmt.Sprintf("不支持策略类型 %s", t))
}
}
func (s *JewelryPackageService) CalcSinglePlan(cond *core.PackQueryCondition) *core.PackResult {
if cond.Constraint != nil {
c := cond.Constraint
s.logger.Info().Msg("=======装箱约束参数=======")
s.logger.Info().Float64("缓冲系数", c.BufferCoeff).Msg("")
s.logger.Info().Int("单盒最大件数", c.SingleBoxMaxQty).Msg("")
s.logger.Info().Bool("禁止混装", c.ForbidMixJewelry).Msg("")
s.logger.Info().Bool("禁止拆盒", c.ForbidSplitJewelry).Msg("")
}
calcVol := 0.0
if len(cond.JewelryList) > 0 {
calcVol = s.CalcJewelryTotalVolume(cond.JewelryList)
cond.TotalMaterialVol = calcVol
}
subConditions := s.SplitByJewelry(cond)
if len(subConditions) == 0 {
s.logger.Error().Msg("所有品类均无法满足约束条件,无法进行配载")
remainStock := make(map[string]int)
for _, pkg := range cond.PackageSpecList {
remainStock[pkg.SpecCode] = pkg.StockQty
}
merge := &core.PackResult{
Code: core.CodeNoAnyLoadPlan.Code,
Msg: "所有品类均无法满足约束条件",
UsePackageList: []core.SingleUsePackage{},
RemainMaterialVol: calcVol,
RemainPackageStock: remainStock,
TotalUsePackageCnt: 0,
SearchStateCount: 0,
FullLoadFlag: false,
TotalCost: 0,
JewelryTotalCalcVol: calcVol,
}
s.PrintResultLog(merge)
return merge
}
strategy := s.CreateStrategy(cond.StrategyType)
pool := concurrency.GetPool()
var subResults []*core.PackResult
for _, sc := range subConditions {
subRes := pool.Submit(strategy, sc)
subResults = append(subResults, subRes)
}
merge := s.MergeSubResult(subResults, cond)
merge.JewelryTotalCalcVol = calcVol
s.PrintResultLog(merge)
return merge
}
// MergeSubResult 合并多个品类独立配载结果
func (s *JewelryPackageService) MergeSubResult(subList []*core.PackResult, origin *core.PackQueryCondition) *core.PackResult {
var usePackages []core.SingleUsePackage
remainStock := make(map[string]int)
remainMatSum := 0.0
totalSearch := 0
totalCostSum := 0.0
allSuccess := true
failedCount := 0
successCount := 0
for _, pkg := range origin.PackageSpecList {
remainStock[pkg.SpecCode] = pkg.StockQty
}
for _, sub := range subList {
if sub.Code != core.CodeSuccess.Code {
allSuccess = false
failedCount++
} else {
successCount++
}
remainMatSum += sub.RemainMaterialVol
totalSearch += sub.SearchStateCount
totalCostSum += sub.TotalCost
usePackages = append(usePackages, sub.UsePackageList...)
for _, pu := range sub.UsePackageList {
remainStock[pu.SpecCode] -= pu.UseCount
}
}
totalPackCnt := 0
for _, pu := range usePackages {
totalPackCnt += pu.UseCount
}
fullFlag := remainMatSum <= 0.01
if !allSuccess {
msg := fmt.Sprintf("部分品类无法找到可行包装方案 (成功: %d, 失败: %d)", successCount, failedCount)
return &core.PackResult{
Code: core.CodeNoAnyLoadPlan.Code,
Msg: msg,
UsePackageList: usePackages,
RemainMaterialVol: remainMatSum,
RemainPackageStock: remainStock,
TotalUsePackageCnt: totalPackCnt,
SearchStateCount: totalSearch,
FullLoadFlag: fullFlag,
TotalCost: totalCostSum,
}
}
return &core.PackResult{
Code: core.CodeSuccess.Code,
Msg: core.CodeSuccess.Msg,
UsePackageList: usePackages,
RemainMaterialVol: remainMatSum,
RemainPackageStock: remainStock,
TotalUsePackageCnt: totalPackCnt,
SearchStateCount: totalSearch,
FullLoadFlag: fullFlag,
TotalCost: totalCostSum,
}
}
func (s *JewelryPackageService) PrintResultLog(res *core.PackResult) {
fmt.Println("=====================配载结果=====================")
fmt.Printf("首饰理论总体积: %.2f cm³\n", res.JewelryTotalCalcVol)
fmt.Printf("返回码: %d, 消息: %s\n", res.Code, res.Msg)
fmt.Printf("搜索状态总数: %d\n", res.SearchStateCount)
fmt.Printf("总共包装盒数量: %d\n", res.TotalUsePackageCnt)
fmt.Printf("方案总成本: %.2f\n", res.TotalCost)
fmt.Printf("全部装载完成: %t\n", res.FullLoadFlag)
fmt.Printf("剩余物料体积: %.2f cm³\n", res.RemainMaterialVol)
fmt.Println("\n【品类配载详情】")
jewelryGroups := make(map[string]map[string]int)
jewelryVolMap := make(map[string]float64)
jewelryTotalUsed := make(map[string]int)
for _, item := range res.UsePackageList {
jewelryCode := item.BelongJewelryCode
if idx := strings.Index(jewelryCode, "_"); idx > 0 {
jewelryCode = jewelryCode[:idx]
}
if _, ok := jewelryGroups[jewelryCode]; !ok {
jewelryGroups[jewelryCode] = make(map[string]int)
}
jewelryGroups[jewelryCode][item.SpecCode] += item.UseCount
jewelryVolMap[item.SpecCode] = item.Volume
jewelryTotalUsed[jewelryCode] += item.UseCount
}
if len(jewelryGroups) == 0 {
fmt.Println(" 无")
} else {
for jewelryCode, specMap := range jewelryGroups {
fmt.Printf("\n 货号: %s\n", jewelryCode)
fmt.Printf(" 已装数量: %d 盒\n", jewelryTotalUsed[jewelryCode])
for specCode, count := range specMap {
fmt.Printf(" └─ 规格: %s, 有效容积: %.2f cm³, 使用数量: %d\n",
specCode, jewelryVolMap[specCode], count)
}
}
}
fmt.Println("\n【包装规格汇总】")
specGroups := make(map[string]int)
specVolMap := make(map[string]float64)
for _, item := range res.UsePackageList {
specGroups[item.SpecCode] += item.UseCount
specVolMap[item.SpecCode] = item.Volume
}
if len(specGroups) == 0 {
fmt.Println(" 无")
} else {
for specCode, count := range specGroups {
fmt.Printf(" 规格: %s, 有效容积: %.2f cm³, 使用数量: %d\n",
specCode, specVolMap[specCode], count)
}
}
fmt.Println("\n【包装库存剩余】")
if len(res.RemainPackageStock) == 0 {
fmt.Println(" 无")
} else {
for code, num := range res.RemainPackageStock {
fmt.Printf(" 规格: %s, 剩余数量: %d\n", code, num)
}
}
fmt.Println("\n==================================================")
}
调用:
Go
/*
# 版权所有 2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search
# 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/7/26 22:16
# User : geovindu
# Product : GoLand
# Project : goalgorithms
# File : breadthfirstbll.go
*/
package bll
import (
"bufio"
"fmt"
"goalgorithms/breadthfirst/core"
"goalgorithms/breadthfirst/service"
"os"
"strconv"
"strings"
)
var scanner = bufio.NewScanner(os.Stdin)
func readString(prompt string) string {
fmt.Print(prompt)
scanner.Scan()
return strings.TrimSpace(scanner.Text())
}
func readFloat(prompt string) float64 {
for {
s := readString(prompt)
f, err := strconv.ParseFloat(s, 64)
if err == nil && f > 0 {
return f
}
fmt.Println("输入错误,请输入大于0数字")
}
}
func readInt(prompt string) int {
for {
s := readString(prompt)
i, err := strconv.Atoi(s)
if err == nil && i >= 0 {
return i
}
fmt.Println("输入错误,请输入非负整数")
}
}
func readBool(prompt string) bool {
for {
s := readString(prompt + " y/n:")
if s == "y" {
return true
} else if s == "n" {
return false
}
fmt.Println("只能输入 y / n")
}
}
func BreadthFirstMain() {
svc := service.NewService()
var jewelryList []core.JewelryItem
var pkgList []core.PackageSpec
var constraint *core.PackConstraint
for {
fmt.Println("\n================珠宝首饰智能包装配载系统 Go版================")
fmt.Println("1. 录入首饰品类清单")
fmt.Println("2. 录入包装规格(容积、成本、优先级权重、库存)")
fmt.Println("3. 设置装箱约束")
fmt.Println("4. BFS配载(推荐)")
fmt.Println("5. DFS配载")
fmt.Println("6. A*启发配载")
fmt.Println("0. 退出")
opt := readString("请输入选项:")
switch opt {
case "1":
jewelryList = nil
for {
code := readString("首饰货号:")
sv := readFloat("单件首饰体积 cm³:")
qty := readInt("品类数量:")
jewelryList = append(jewelryList, core.JewelryItem{
JewelryCode: code,
SingleVolume: sv,
Quantity: qty,
})
again := readBool("继续录入首饰?")
if !again {
break
}
}
case "2":
pkgList = nil
for {
code := readString("包装编码:")
vol := readFloat("标称容积 cm³:")
cost := readFloat("单个包装成本:")
weight := readInt("优先级权重(1~100):")
stock := readInt("可用库存:")
pkgList = append(pkgList, core.PackageSpec{
SpecCode: code,
Volume: vol,
UnitCost: cost,
PriorityWeight: weight,
StockQty: stock,
})
again := readBool("继续录入包装?")
if !again {
break
}
}
case "3":
bufCoeff := readFloat("缓冲空隙系数(0.7~0.95):")
maxBoxQty := readInt("单个礼盒最大首饰件数:")
forbidMix := readBool("禁止不同品类混装?")
forbidSplit := readBool("禁止同一品类拆分多个盒子?")
constraint = &core.PackConstraint{
BufferCoeff: bufCoeff,
SingleBoxMaxQty: maxBoxQty,
ForbidMixJewelry: forbidMix,
ForbidSplitJewelry: forbidSplit,
}
case "4", "5", "6":
if len(jewelryList) == 0 || len(pkgList) == 0 || constraint == nil {
fmt.Println("请先录入首饰、包装、装箱约束!")
continue
}
var st core.LoadStrategyType
switch opt {
case "4":
st = core.StrategyBFS
case "5":
st = core.StrategyDFS
case "6":
st = core.StrategyAStar
}
cond := &core.PackQueryCondition{
JewelryList: jewelryList,
Constraint: constraint,
PackageSpecList: pkgList,
StrategyType: st,
AllowOverLoad: false,
}
_ = svc.CalcSinglePlan(cond)
case "0":
fmt.Println("程序退出")
return
default:
fmt.Println("无效选项")
}
}
}
输出:
