62.状态机实践(活动管理系统:二)

文章目录

  • 一、简介
  • 二、状态机实践(活动元信息管理)
    • 1、dal/db.go
    • 2、dal/activity.go
    • 3、constdef/activity.go
    • 4、service/activity.go
    • 5、routes/routes.go
    • 6、main.go

代码地址:https://gitee.com/lymgoforIT/golang-trick/tree/master/37-load-local-cache

一、简介

在上一节61.本地缓存加载与使用实践(活动管理系统:一)中,我们以活动元信息为例介绍了本地缓存的加载实践,本节想介绍一下另一个非常常见的编码技巧实践:状态机

之前本人已经有好几篇文章都介绍过状态机啦,但都有点单一,所以这次想举个具体的例子,结合Gin搞个实践示例,当然,主要还是介绍编码套路,一些简单的CRUD函数不会写的很详细

之前的文章地址如下:
22.有限状态机(一)go语言fsm库
23.有限状态机(二)状态模式实现
24.有限状态机(三)表驱动法Go实现

二、状态机实践(活动元信息管理)

首先看下状态机要实现的效果
在这里插入图片描述

  • 其中方框表示状态,箭头表示事件和状态流转
  • 共有空白、草稿、测试中、上线审核中、运行中、下线审核中、已失效七种状态
  • 绿色的为最基本的,也是本次要做的,蓝色框出的不一定需要,所以本次为了简洁就没有处理,即使要处理,理解了本文介绍的基本模型,新增状态是很简单的

实现后代码目录结构如下
在这里插入图片描述

1、dal/db.go

上节中是直接模拟的和DB交互,本节我们就引入Gorm和真实DB交互,代码比较简单,定义初始化DB的方法,以及定义DB全局变量与获取它的GetDB函数

package dal

import (
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

var DB *gorm.DB

func InitDB() error {
	dsn := "root:root@(127.0.0.1:3306)/activity?charset=utf8mb4&parseTime=true&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})

	if err != nil {
		return err
	}
	DB = db
	return nil
}

func GetDB() *gorm.DB {
	return DB
}

2、dal/activity.go

Activity model相关的与DB交互的函数都写到该文件中,代码非常简单,需要重点强调的是GetActivitiesByParam函数,这也是工作中非常常用的一个编码技巧,对应下面小结中的第2点。这里小结一下和DB交互的几种常见方式吧!

  1. 根据指定字段或者ID获取记录,常用于脚本或一些单一的查询。
  2. 定义和model字段很像的结构体(常外加分页相关字段),根据结构体中各字段的有无拼SQL,常用于元信息管理系统,有多个搜索框的页面。
  3. 函数式选项模式,常用于需要非常灵活的拼SQL,且需要很强的扩展性的场景。
package dal

import (
	"errors"
	"golang-trick/37-load-local-cache/model"
	"time"
)

// GetActivity 从DB获取活动元信息
func GetActivitiesForLocalCache(minId int64, status []int, batchSize int) ([]*model.Activity, error) {
	//return []*model.Activity{
	//	{
	//		Id:         1,
	//		Name:       "限时返场",
	//		Type:       1, // 枚举更好,此处就把1当成返场类型
	//		ProductId:  1,
	//		Desc:       "返场描述",
	//		Status:     1, // 枚举更好,此处就把1当成生效中
	//		Rules:      "",
	//		StartTime:  time.Time{},
	//		EndTime:    time.Time{},
	//		CreateTime: time.Time{},
	//		UpdateTime: time.Time{},
	//	},
	//	{
	//		Id:         2,
	//		Name:       "极速秒杀",
	//		Type:       2, // 秒杀类型
	//		ProductId:  1,
	//		Desc:       "秒杀描述",
	//		Status:     1,
	//		Rules:      "",
	//		StartTime:  time.Time{},
	//		EndTime:    time.Time{},
	//		CreateTime: time.Time{},
	//		UpdateTime: time.Time{},
	//	},
	//}, nil

	db := GetDB()

	var activities []*model.Activity
	db.Debug().Where("id >?", minId).Where("status in?", status).Limit(batchSize).Order("id").Find(&activities)
	return activities, nil

}

func GetActivityById(id int64) (*model.Activity, error) {
	db := GetDB()

	var activity model.Activity
	db.Debug().Where("id =?", id).Find(&activity)
	return &activity, nil
}

// 较通用的方法,使用结构体做为参数,将不为空的字段拼到SQL中
type GetActivitiesParam struct {
	Id        int64      // 活动ID
	Name      string     // 活动名称
	Type      int        // 活动类型
	ProductId int64      // 产品线
	Desc      string     // 描述
	Status    []int      // 活动状态
	Rules     string     // 活动规则
	StartTime *time.Time // 开始时间
	EndTime   *time.Time // 结束时间
	PageNum   int        // 分页参数
	PageSize  int
}

func GetActivitiesByParam(param GetActivitiesParam) ([]*model.Activity, int64, error) {
	db := GetDB()

	out := make([]*model.Activity, 0)

	if param.Id != 0 {
		db = db.Where("id =?", param.Id)
	}
	if param.Name != "" {
		db = db.Where("name =?", param.Name)
	}
	if param.Type != 0 {
		db = db.Where("type =?", param.Type)
	}
	if param.ProductId != 0 {
		db = db.Where("product_id =?", param.ProductId)
	}
	if param.Desc != "" {
		db = db.Where("desc =?", param.Desc)
	}
	if len(param.Status) != 0 {
		db = db.Where("status in?", param.Status)
	}
	if param.StartTime != nil {
		db = db.Where("start_time >?", param.StartTime)
	}
	if param.EndTime != nil {
		db = db.Where("end_time < ?", param.EndTime)
	}

	var total int64
	// 如果传了合法的分页参数,则要进行分页查询,而分页查询一般都需要返回相应总条数,从而前端好分页显示并展示总条数以及页数
	if param.PageNum != 0 && param.PageSize != 0 {
		realPageNum := param.PageNum - 1
		if realPageNum < 0 {
			return nil, 0, errors.New("PageNum is invalid")
		}
		db.Model(model.Activity{}).Count(&total)
		db = db.Order("id").Offset(realPageNum * param.PageSize).Limit(param.PageSize)
	}

	err := db.Find(&out).Error
	return out, total, err
}

3、constdef/activity.go

对于常量,我们一般习惯专门定义到相应的常量文件中,或者定义到要使用该常量的文件开头,这里选用了前者的方式,单独定义到文件中。

package constdef

// 活动状态的枚举
type ActivityStatusEnum int

const (
	ActivityStatusEnum_Blank           ActivityStatusEnum = 0 // 空白态
	ActivityStatusEnum_Draft           ActivityStatusEnum = 1 // 草稿
	ActivityStatusEnum_OnlineApproval  ActivityStatusEnum = 2 // 上线审批中
	ActivityStatusEnum_OfflineApproval ActivityStatusEnum = 3 // 下线审批中
	ActivityStatusEnum_Running         ActivityStatusEnum = 4 // 运行中
	ActivityStatusEnum_Stop            ActivityStatusEnum = 5 // 已失效
	ActivityStatusEnum_Testing         ActivityStatusEnum = 6 // 测试中
)

4、service/activity.go

在介绍路由前,首先介绍service,因为路由不过就是调用service里面的方法罢了,状态机以及其他的一些和Activity相关的路由方法就在该文件中。

状态机以及SaveActivity方法

  • ActivityService结构体里面包含了状态机,使用ActivityService时都应该用NewActivityService获得结构体对象,因为这个new方法中才会初始化状态机
  • 我们提供了RegisterHandler方法注册状态机handler,也提供了GetHandlerByState方法获取对应的handler执行业务逻辑。具体的解释看代码和注释更为清晰。
  • 状态机的使用在SaveActivity方法中,现金、编辑更新、提测、申请上线等都是调用这个方法就行,然后根据初态、次态获取对应的handler执行
  • 具体的创建、编辑更新、提测、申请上线等handler的逻辑,需要根据具体业务场景而定,这里就没有写了。比如创建需要考虑ID的生成是否用ID生成器,编辑需要校验编辑人是否有编辑权限,申请上线可能需要发起审批流水线等。

此外该service中还提供了与其他一些路由相对应的方法,如

  • GetActivities :根据条件获取活动列表
  • GetActivityDetailById :根据活动ID获取活动详情
  • OnlineApproval :上线审批回调

注意看注释哦!!

package service

import (
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"golang-trick/37-load-local-cache/constdef"
	"golang-trick/37-load-local-cache/dal"
	"golang-trick/37-load-local-cache/model"
	"net/http"
)

type ActivityHandler func(beforeActivityInfo, afterActivityInfo *model.Activity) error

type ActivityService struct {
	// 该状态机是针对活动的,只会在该包下使用,所以作为非导出字段
	stateMachine map[string]ActivityHandler
}

func NewActivityService() *ActivityService {
	as := &ActivityService{
		stateMachine: make(map[string]ActivityHandler),
	}
	// 注册状态机
	as.RegisterHandler(constdef.ActivityStatusEnum_Blank, constdef.ActivityStatusEnum_Draft, as.CreateActivity)
	as.RegisterHandler(constdef.ActivityStatusEnum_Draft, constdef.ActivityStatusEnum_Draft, as.UpdateActivity)
	as.RegisterHandler(constdef.ActivityStatusEnum_Draft, constdef.ActivityStatusEnum_Testing, as.TestActivity)
	as.RegisterHandler(constdef.ActivityStatusEnum_Testing, constdef.ActivityStatusEnum_Draft, as.UpdateActivity)
	as.RegisterHandler(constdef.ActivityStatusEnum_Testing, constdef.ActivityStatusEnum_OnlineApproval, as.SubmitOnlineApproval)
	as.RegisterHandler(constdef.ActivityStatusEnum_OnlineApproval, constdef.ActivityStatusEnum_Draft, as.OnlineApprovalReject)
	as.RegisterHandler(constdef.ActivityStatusEnum_OnlineApproval, constdef.ActivityStatusEnum_Running, as.RunningActivity)
	return as
}

// RegisterHandler 注册状态机
// before状态从DB获取,after状态由前端传入,省去了事件元素,让前端感知用户事件推到次态,然后直接将次态给后端
// 注:完整的状态机一般会包含如下四个元素  初态   事件  次态  动作,通过初态和事件决定次态以及要执行的动作,但我们这里是变形写法
// 初态后端自己查DB中的,次态前端给,然后根据 初态_次态 决定 动作
func (as *ActivityService) RegisterHandler(before, after constdef.ActivityStatusEnum, handler ActivityHandler) {
	stateChange := fmt.Sprintf("%d_%d", before, after)
	as.stateMachine[stateChange] = handler
}

// GetHandlerByState 根据状态机获取任务类型处理器
func (as *ActivityService) GetHandlerByState(before, after constdef.ActivityStatusEnum) (ActivityHandler, error) {
	stateChange := fmt.Sprintf("%d_%d", before, after)
	if as.stateMachine == nil {
		return nil, errors.New("stateMachine not data_init")
	}
	handler, ok := as.stateMachine[stateChange]
	if ok {
		return handler, nil
	}
	return nil, errors.New(fmt.Sprintf("the update status is incorrect %s, the process does not exist", stateChange))
}

// SaveActivity 是一个通用的入口,里面包含了状态机,创建、更新、提测等改变活动元信息的都从此处进入,当然:审批除外,审批的也会改变活动元信息状态,但是是通过回调实现的
func (as *ActivityService) SaveActivity(ctx *gin.Context) {
	// 从ctx获取参数afterActivityInfo、以及根据业务诉求做一些参数校验,这里都省去了
	afterActivityInfo := &model.Activity{}

	// 初态默认为空白,比如新建
	beforeStatus := constdef.ActivityStatusEnum_Blank
	afterStatus := constdef.ActivityStatusEnum(afterActivityInfo.Status)
	beforeActivityInfo := &model.Activity{}
	if afterActivityInfo.Id != 0 {
		// 已有的活动,查询DB,获取DB中改活动的初态
		res, err := dal.GetActivityById(afterActivityInfo.Id)
		if err != nil {
			ctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
		}
		beforeStatus = constdef.ActivityStatusEnum(res.Status)
		beforeActivityInfo = res
	}
	handler, err := as.GetHandlerByState(beforeStatus, afterStatus)
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
	}

	如果不是新建则需要校验写权限
	//if beforeStatus != constdef.ActivityStatusEnum_Blank {
	//
	//	err = as.CheckTaskTypeWriterAuth(afterActivityInfo)
	//	if err != nil {
	//		return err
	//	}
	//}

	err = handler(beforeActivityInfo, afterActivityInfo)
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
	}
	ctx.JSON(http.StatusOK, gin.H{
		"message": "pong",
	})
}

// CreateActivity 创建活动信息
func (as *ActivityService) CreateActivity(beforeActivityInfo, afterActivityInfo *model.Activity) error {
	// 可能还有很多校验逻辑与业务逻辑,最后调用 dal.Save(afterActivityInfo)创建记录
	// 注:gorm中Save会先通过主键find,如果有返回结果就是update,否则就是create
	return nil
}

// UpdateActivity 编辑活动信息
func (as *ActivityService) UpdateActivity(beforeActivityInfo, afterActivityInfo *model.Activity) error {
	// 可能还有很多校验逻辑与业务逻辑,最后调用 dal.Save(afterActivityInfo)创建记录
	// 注:gorm中Save会先通过主键find,如果有返回结果就是update,否则就是create
	return nil
}

// TestActivity 活动信息提测
func (as *ActivityService) TestActivity(beforeActivityInfo, afterActivityInfo *model.Activity) error {
	return nil
}

// SubmitOnlineApproval 活动信息申请上线 ,一般应该发起流水线或者其他方式的审批
func (as *ActivityService) SubmitOnlineApproval(beforeActivityInfo, afterActivityInfo *model.Activity) error {
	return nil
}

// OnlineApprovalReject 审批驳回,在回调方法OnlineApproval里面使用
func (as *ActivityService) OnlineApprovalReject(beforeActivityInfo, afterActivityInfo *model.Activity) error {
	return nil
}

// RunningActivity 审批通过,在回调方法OnlineApproval里面使用
func (as *ActivityService) RunningActivity(beforeActivityInfo, afterActivityInfo *model.Activity) error {
	return nil
}

// OnlineApproval 上线审批回调
func (as *ActivityService) OnlineApproval(ctx *gin.Context) {
	// 实际业务代码beforeActivityInfo和afterActivityInfo应该从ctx的Param中取的
	beforeActivityInfo := &model.Activity{}
	afterActivityInfo := &model.Activity{}
	approve := true // 审批结果,即是通过还是拒绝也应该从ctx的参数中取,这里写死为通过了
	var err error
	if approve {
		err = as.RunningActivity(beforeActivityInfo, afterActivityInfo)
	} else {
		err = as.OnlineApprovalReject(beforeActivityInfo, afterActivityInfo)
	}

	if err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
	}

	ctx.JSON(http.StatusOK, gin.H{
		"message": "pong",
	})
}

// GetActivities 根据条件获取活动列表
func (as *ActivityService) GetActivities(ctx *gin.Context) {
	// 实际业务代码param应该从ctx的Param中取的
	param := dal.GetActivitiesParam{}
	activities,total, err := dal.GetActivitiesByParam(param)
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
	}

	ctx.JSON(http.StatusOK, gin.H{
		"message": activities,
		"total":total,
	})
}

// GetActivityDetailById 根据活动ID获取活动详情
func (as *ActivityService) GetActivityDetailById(ctx *gin.Context) {
	// 实际业务代码id应该从ctx的Param中取的
	id := int64(1)
	activity, err := dal.GetActivityById(id)

	if err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
	}

	ctx.JSON(http.StatusOK, gin.H{
		"message": activity,
	})
}

5、routes/routes.go

路由我们也专门拆出了目录和文件管理,而不是写到main.go中,这在之前的一些博客中也都介绍过啦,如:59.Gin框架路由拆分与注册。

主要涵盖了以下路由,基本包含了对于一个活动元信息的状态机全流程

  • 查询活动信息列表路由,对应前端一个列表管理与查询页面
  • 根据ID查询详情路由,对应前端一个查看详情页面
  • 针对状态机的save_activity路由,涵盖了前端的新建、编辑更新、提测、申请上线等请求
  • activity_online_approval路由则主要用于上线审批结果的回调,对应状态机中的申请上线中、运行中以及草稿三个状态的流转。
package routers

import (
	"github.com/gin-gonic/gin"
	"golang-trick/37-load-local-cache/service"
	"net/http"
)

func Init() *gin.Engine {
	r := gin.Default()

	// 获取活动元信息列表(可分页获取)
	r.GET("/activity", service.NewActivityService().GetActivities)

	// 根据活动ID获取指定活动详情
	r.GET("/activity_detail/:id", service.NewActivityService().GetActivityDetailById)

	// 新建、编辑更新、提测、申请上线等都请求的该路由,【状态机在该路由中】
	r.POST("/save_activity", service.NewActivityService().SaveActivity)

	// 上线审批结果,实际工作中,应该是流水线回调或者飞书、微信或钉钉等审批回调
	r.POST("/activity_online_approval", service.NewActivityService().OnlineApproval)

	// 下线审批结果,实际工作中,应该是流水线回调或者飞书、微信或钉钉等审批回调
	r.POST("/activity_offline_approval", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "pong",
		})
	})

	return r
}

6、main.go

main文件中一般都会比较简洁,主要做资源加载,服务启动等工作。

这里就是做的DB初始化、本地缓存加载、路由注册、服务启动等工作

package main

import (
	"golang-trick/37-load-local-cache/cache"
	"golang-trick/37-load-local-cache/dal"
	"golang-trick/37-load-local-cache/routers"
)

func main() {
	// 初始化DB
	err := dal.InitDB()
	if err != nil {
		panic(err)
	}

	// 加载本地缓存
	err = cache.LoadActivity()
	if err != nil {
		panic(err)
	}
	cache.RefreshCache()

	// gin 路由注册
	r := routers.Init()

	// 服务启动
	r.Run(":8080")
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/293467.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

详细解读QLC SSD无效编程问题-4

对于这些全部页面被无效化的WL&#xff0c;执行第二次编程实际上是不必要的&#xff0c;但当前的策略并未注意到这一问题。而对于那些既有有效页面又有无效页面&#xff08;图11中显示为1到3个&#xff09;的WL&#xff0c;应当被编程&#xff0c;但可以利用这些无效信息来改进…

C++设计模式 #8 抽象工厂(Abstract Factory)

抽象工厂这个名字比较难以帮助理解&#xff0c;可以把抽象工厂理解为“品牌工厂”或者“家族工厂”。 动机 在软件系统中&#xff0c;经常面临着“一系列相互依赖的对象”的创建工作&#xff1b;同时&#xff0c;由于需求的变化&#xff0c;往往存在更多系列对象的创建工作。如…

【Python可视化实战】钻石数据可视化

一、项目引言 1.背景和目标 钻石作为一种珍贵的宝石&#xff0c;其价格受到多种因素的影响。为了深入了解钻石价格的决定因素&#xff0c;我们收集了大量关于钻石的数据&#xff0c;并希望通过数据可视化来揭示钻石特征与价格之间的关系。 2.内容 收集钻石的各项特征数据&a…

【python高级用法】进程

一个简单的进程 # -*- coding: utf-8 -*-import multiprocessingdef foo(i):print (called function in process: %s %i)returnif __name__ __main__:Process_jobs []for i in range(5):p multiprocessing.Process(targetfoo, args(i,))Process_jobs.append(p)p.start()p.j…

Vue中的过滤器详解(应用场景和原理分析)

文章目录 一、是什么二、如何用定义filter小结&#xff1a; 三、应用场景四、原理分析小结&#xff1a; 参考文献 一、是什么 过滤器&#xff08;filter&#xff09;是输送介质管道上不可缺少的一种装置 大白话&#xff0c;就是把一些不必要的东西过滤掉 过滤器实质不改变原…

K-最近邻算法(KNN)是什么算法?

K-最近邻算法&#xff08;K-Nearest Neighbor&#xff0c;KNN&#xff09;是一种经典的有监督学习方法&#xff0c;也可以被归为懒惰学习&#xff08;Lazy Learning&#xff09;方法。它基于“物以类聚”的原理&#xff0c;假设样本之间的类别距离越近则它们越有可能是同一类别…

关于目标检测任务中,XML(voc格式)标注文件的可视化

1. 前言 最近在弄关于目标检测的任务&#xff0c;因为检测的图片和标签是分开的&#xff0c;可视化效果不明显&#xff0c;也不知道随便下载的数据集&#xff0c;标注信息对不对。网上看了好多代码&#xff0c;代码风格和本人平时不同&#xff0c;看起来麻烦&#xff0c;也不知…

项目使用PowerJob

新一代的定时任务框架——PowerJob 简介 PowerJob是基于java开发的企业级的分布式任务调度平台&#xff0c;与xxl-job一样&#xff0c;基于web页面实现任务调度配置与记录&#xff0c;使用简单&#xff0c;上手快速&#xff0c;其主要功能特性如下&#xff1a; 使用简单&…

ClickHouse基础介绍

目录 前言 1、什么是clickhouse 2、OLAP场景的关键特征 3、列式存储更适合于OLAP场景的原因 4、clickhouse的独特功能 5、clickhouse的缺点 6、性能 6.1、单个大查询的吞吐量 6.2、处理短查询的延迟时间 6.3、处理大量短查询的吞吐量 6.4、数据的写入性能 前言 11月…

RTSP/Onvif安防平台EasyNVR接入EasyNVS显示服务不存在的原因及解决办法

EasyNVS云管理平台具备汇聚与管理EasyGBS、EasyNVR等平台的能力&#xff0c;可以将接入的视频资源实现统一的视频能力输出&#xff0c;支持远程可视化运维等管理功能&#xff0c;还能解决设备现场没有固定公网IP却需要在公网直播的需求。 有用户在现场部署EasyNVR&#xff0c;…

how2heap-2.23-04-unsorted_bin_leak

#include<stdio.h> #include<malloc.h>int main() {char* a malloc(0x88);char* b malloc(0x8);free(a);long* c malloc(0x88);printf("%lx , %lx\n",c[0],c[1]);return 0; }unsorted bin leak原理&#xff1a;将chunk从unsorted bin申请回来时&#…

ssm基于web的素材网的设计与实现+vue论文

基于web的素材网站的设计与实现 摘要 当下&#xff0c;正处于信息化的时代&#xff0c;许多行业顺应时代的变化&#xff0c;结合使用计算机技术向数字化、信息化建设迈进。传统的素材信息管理模式&#xff0c;采用人工登记的方式保存相关数据&#xff0c;这种以人力为主的管理…

安卓逆向某脚本-stringFog 拆解

引言 有个autojs 脚本软件,挺好用的,我想看下这个软件怎么实现的,学习写人家怎么写的。 先用MT 重新打包下, 看下重打包之后是否还可以继续使用。 用MT 打开APK,然后选择查看 随便找一个dex ,编辑下

02、Kafka ------ 配置 Kafka 集群

目录 配置 Kafka 集群配置步骤启动各Kafka节点 配置 Kafka 集群 启动命令&#xff1a; 1、启动 zookeeper 服务器端 小黑窗输入命令&#xff1a; zkServer 2、启动 zookeeper 的命令行客户端工具 &#xff08;这个只是用来看连接的节点信息&#xff0c;不启动也没关系&#…

数据结构与算法(六)

文章目录 高频-体系学习班(六)41 四边形不等式技巧(上)41.1 非负数组切分成左右两部分累加和的最大值41.2 非负数组切分成左右两部分累加和的最大值的数组41.3 合并石子的得分41.4 画匠问题42 四边形不等式技巧(下)42.1 邮局选址问题42.2 丢棋子问题43 状态压缩的动态规划…

RabbitMQ安装与应用

文章目录 1. RabbitMQ1.1. 同步通讯与异步通讯1.2. 异步通讯的优缺点1.3. 几种MQ的对比1.4. docker安装运行RabbitMQ 流程1.5. RabbitMQ的几个概念1.6. 五种模型1.6.1. 基本消息队列 1.7. 基本使用1.7.1. 1建立连接时会出现以下界面![在这里插入图片描述](https://img-blog.csd…

MFC扩展库BCGControlBar Pro v34.0 - 网格、报表控件功能升级

BCGControlBar库拥有500多个经过全面设计、测试和充分记录的MFC扩展类。 我们的组件可以轻松地集成到您的应用程序中&#xff0c;并为您节省数百个开发和调试时间。 BCGControlBar专业版 v34.0已正式发布了&#xff0c;该版本包括新的主题任务对话框、图像效果、旋转圆形刻度、…

[电子榨菜]状态管理redux,以及react-redux

0.写在前面 很遗憾&#xff0c;最终还是没能入围2023年的博客评选。 不过不管怎么说&#xff0c;今年需要开个好头。 迫于成本压力吧&#xff0c;最终还是没能顺利离开这里。。。。。。 其实白天已经能放的下啦&#xff0c;我给自己买了喜欢的玩具&#xff0c;去了喜欢的漫…

MySQL:约束主键唯一键

表的约束&#xff1a;表中一定有约束&#xff0c;通过约束让插入表中的数据是符号预期的 约束的本质是通过技术手段&#xff0c;倒逼程序员插入正确的数据 Null约束 这里的Null表示在插入的时候&#xff0c;该属性能否为空&#xff0c;如果是NO&#xff0c;则插入时候必须有数…

《Effective C++》《Resource Management》

文章目录 13、term13:Use objects to manage resources14、term14:Think carefully about copying behavior in resource-managing classes15、term15:Provide access to raw resources in resource-managing classes法一&#xff1a; 使用智能指针的get进行显示转换法二&#…
最新文章