【GoTeams】-2:項目基礎搭建(下)

在這里插入圖片描述

本文目錄

  • 1. 回顧
  • 2. Zap日志
  • 3. 配置
  • 4. 引入gprc
    • 梳理gRPC思路
    • 優雅關閉gRPC

1. 回顧

上篇文章我們進行了路由搭建,引入了redis,現在來看看對應的效果。

首先先把前端跑起來,然后點擊注冊獲取驗證碼。

在這里插入圖片描述

再看看控制臺輸出和redis是否已經有記錄,驗證沒問題,現在redis這個環節是打通了。

在這里插入圖片描述

2. Zap日志

go中原生的日志比較一般,我們可以集成一個流行的日志庫進來。

這里用uber開源的zap日志庫,在common路徑下安裝zap:go get -u go.uber.org/zap。

然后再安裝一個日志分割庫,go get -u github.com/natefinch/lumberjack,因為日志的存儲有幾種方式,比如按照日志級別將日志記錄到不同的文件,按照業務來分別記錄不同級別的日志,按照包結構劃分記錄不同級別日志。debug級別以上記錄一個,info以上記錄一個,warn以上記錄一個

common路徑下創建logs.go,然后編寫對應的代碼。

package logsimport ("github.com/gin-gonic/gin""github.com/natefinch/lumberjack""go.uber.org/zap""go.uber.org/zap/zapcore""net""net/http""net/http/httputil""os""runtime/debug""strings""time"
)var lg *zap.Loggertype LogConfig struct {DebugFileName string `json:"debugFileName"`InfoFileName  string `json:"infoFileName"`WarnFileName  string `json:"warnFileName"`MaxSize       int    `json:"maxsize"`MaxAge        int    `json:"max_age"`MaxBackups    int    `json:"max_backups"`
}// InitLogger 初始化Logger
func InitLogger(cfg *LogConfig) (err error) {writeSyncerDebug := getLogWriter(cfg.DebugFileName, cfg.MaxSize, cfg.MaxBackups, cfg.MaxAge)writeSyncerInfo := getLogWriter(cfg.InfoFileName, cfg.MaxSize, cfg.MaxBackups, cfg.MaxAge)writeSyncerWarn := getLogWriter(cfg.WarnFileName, cfg.MaxSize, cfg.MaxBackups, cfg.MaxAge)encoder := getEncoder()//文件輸出debugCore := zapcore.NewCore(encoder, writeSyncerDebug, zapcore.DebugLevel)infoCore := zapcore.NewCore(encoder, writeSyncerInfo, zapcore.InfoLevel)warnCore := zapcore.NewCore(encoder, writeSyncerWarn, zapcore.WarnLevel)//標準輸出consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())std := zapcore.NewCore(consoleEncoder, zapcore.Lock(os.Stdout), zapcore.DebugLevel)core := zapcore.NewTee(debugCore, infoCore, warnCore, std)lg = zap.New(core, zap.AddCaller())zap.ReplaceGlobals(lg) // 替換zap包中全局的logger實例,后續在其他包中只需使用zap.L()調用即可return
}func getEncoder() zapcore.Encoder {encoderConfig := zap.NewProductionEncoderConfig()encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoderencoderConfig.TimeKey = "time"encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoderencoderConfig.EncodeDuration = zapcore.SecondsDurationEncoderencoderConfig.EncodeCaller = zapcore.ShortCallerEncoderreturn zapcore.NewJSONEncoder(encoderConfig)
}func getLogWriter(filename string, maxSize, maxBackup, maxAge int) zapcore.WriteSyncer {lumberJackLogger := &lumberjack.Logger{Filename:   filename,MaxSize:    maxSize,MaxBackups: maxBackup,MaxAge:     maxAge,}return zapcore.AddSync(lumberJackLogger)
}// GinLogger 接收gin框架默認的日志
func GinLogger() gin.HandlerFunc {return func(c *gin.Context) {start := time.Now()path := c.Request.URL.Pathquery := c.Request.URL.RawQueryc.Next()cost := time.Since(start)lg.Info(path,zap.Int("status", c.Writer.Status()),zap.String("method", c.Request.Method),zap.String("path", path),zap.String("query", query),zap.String("ip", c.ClientIP()),zap.String("user-agent", c.Request.UserAgent()),zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),zap.Duration("cost", cost),)}
}// GinRecovery recover掉項目可能出現的panic,并使用zap記錄相關日志
func GinRecovery(stack bool) gin.HandlerFunc {return func(c *gin.Context) {defer func() {if err := recover(); err != nil {// Check for a broken connection, as it is not really a// condition that warrants a panic stack trace.var brokenPipe boolif ne, ok := err.(*net.OpError); ok {if se, ok := ne.Err.(*os.SyscallError); ok {if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {brokenPipe = true}}}httpRequest, _ := httputil.DumpRequest(c.Request, false)if brokenPipe {lg.Error(c.Request.URL.Path,zap.Any("error", err),zap.String("request", string(httpRequest)),)// If the connection is dead, we can't write a status to it.c.Error(err.(error)) // nolint: errcheckc.Abort()return}if stack {lg.Error("[Recovery from panic]",zap.Any("error", err),zap.String("request", string(httpRequest)),zap.String("stack", string(debug.Stack())),)} else {lg.Error("[Recovery from panic]",zap.Any("error", err),zap.String("request", string(httpRequest)),)}c.AbortWithStatus(http.StatusInternalServerError)}}()c.Next()}
}

然后在main.go中來初始化我們的日志。

在這里插入圖片描述
然后可以把對應的log地方進行更改了,比如下面的地方。
在這里插入圖片描述
然后來驗證一下是否能正常生成日志文件,正常生成了,沒問題。
正常生成了

3. 配置

日志我們用了zap做集成,算是一個改進,但是配置比較復雜, 所以我們這里需要進一步優化這個配置。

配置我們引入viper進行操作,也非常簡單,直接上圖和代碼吧,在user里邊裝viper這個包。

go get github.com/spf13/viper

首先在user下面創建cofig目錄,然后創建config.yaml配置文件,然后創建config.go代碼讀取配置。

在這里插入圖片描述

config.go的代碼如下所示。

package configimport ("github.com/go-redis/redis/v8""github.com/spf13/viper""log""os""test.com/project-common/logs"
)var C = InitConfig()type Config struct {viper *viper.ViperSC    *ServerConfig
}type ServerConfig struct {Name stringAddr string
}func InitConfig() *Config {conf := &Config{viper: viper.New()}workDir, _ := os.Getwd()conf.viper.SetConfigName("config")conf.viper.SetConfigType("yaml")conf.viper.AddConfigPath(workDir + "/config")conf.viper.AddConfigPath("etc/msproject/user")//讀取configerr := conf.viper.ReadInConfig()if err != nil {log.Fatalln(err)}conf.ReadServerConfig()conf.InitZapLog() //初始化zap日志return conf
}func (c *Config) InitZapLog() {//從配置中讀取日志配置,初始化日志lc := &logs.LogConfig{DebugFileName: c.viper.GetString("zap.debugFileName"),InfoFileName:  c.viper.GetString("zap.infoFileName"),WarnFileName:  c.viper.GetString("zap.warnFileName"),MaxSize:       c.viper.GetInt("maxSize"),MaxAge:        c.viper.GetInt("maxAge"),MaxBackups:    c.viper.GetInt("maxBackups"),}err := logs.InitLogger(lc)if err != nil {log.Fatalln(err)}
}func (c *Config) ReadServerConfig() {sc := &ServerConfig{}sc.Name = c.viper.GetString("server.name")sc.Addr = c.viper.GetString("server.addr")c.SC = sc
}// 讀redis的配置
func (c *Config) ReadRedisConfig() *redis.Options {return &redis.Options{Addr:     c.viper.GetString("redis.host") + ":" + c.viper.GetString("redis.port"),Password: c.viper.GetString("redis.password"), // no password setDB:       c.viper.GetInt("db"),                // use default DB}
}

對應的redis.go中原本new一個redis客戶端的代碼也需要更改了,改為已有的讀取配置的函數 ReadRedisConfig()

在這里插入圖片描述
并且把原來main.go中關于zap的相關配置文件刪除即可。
在這里插入圖片描述
然后重新啟動下,看看是否能夠運行,ok,啟動沒問題。

在這里插入圖片描述

4. 引入gprc

可以通過引入一個API把對應的服務連起來,可以把各種服務提出來,然后通過API進行定義。

在這里插入圖片描述
api\proto下新建一個名為login.service.proto的文件,然后編寫代碼。

syntax = "proto3";
package login.service.v1;
option go_package = "project-user/pkg/service/login.service.v1";message CaptchaMessage {string mobile = 1;
}
message CaptchaResponse{
}
service LoginService {rpc GetCaptcha(CaptchaMessage) returns (CaptchaResponse) {}
}

然后在proto路徑下,運行命令:protoc --go_out=./gen --go_opt=paths=source_relative --go-grpc_out=./gen --go-grpc_opt=paths=source_relative login_service.proto,就可以生成對應文件了。

因為是第一版,所以我們先在gen下生成,然后復制移動到service下面,防止后面不斷根據功能進行修改,而導致新生成的被覆蓋。

那么我們來看看這login-service_grpc.pb.go文件到底生成了什么東西。

在這里插入圖片描述


LoginServiceClient 是一個接口,定義了客戶端可以調用的 GetCaptcha 方法。該方法接收一個 CaptchaMessage 請求,返回一個 CaptchaResponse 響應。

loginServiceClientLoginServiceClient 的具體實現,通過 NewLoginServiceClient 函數創建。它使用 grpc.ClientConnInterface 來發起 RPC 調用。

loginServiceClient.GetCaptcha 方法中,通過 c.cc.Invoke 發起 GetCaptcha 方法的 RPC 調用。它將請求數據序列化并發送到服務器,然后等待響應。

在這里插入圖片描述

LoginServiceServer 是服務器端的接口,定義了 GetCaptcha 方法。所有實現該接口的服務器端邏輯必須嵌入 UnimplementedLoginServiceServer,以確保向前兼容性。
UnimplementedLoginServiceServer 提供了一個默認的未實現方法的錯誤響應,返回 codes.Unimplemented 狀態碼。

在這里插入圖片描述

也就屙是說,接口還包含一個方法 mustEmbedUnimplementedLoginServiceServer(),這是一個空方法,用于確保實現者嵌入了 UnimplementedLoginServiceServer

這是 UnimplementedLoginServiceServerGetCaptcha 方法的默認實現。它返回 nil 作為響應,并通過 status.Errorf 返回一個帶有 codes.Unimplemented 狀態碼的錯誤,表明該方法未被實現。這種設計確保了即使服務實現者沒有實現某些方法,調用這些方法時也不會導致程序崩潰,而是返回一個明確的錯誤。

mustEmbedUnimplementedLoginServiceServer() 是一個空方法,用于確保服務實現者嵌入了 UnimplementedLoginServiceServer

testEmbeddedByValue() 是一個輔助方法,用于在運行時檢查 UnimplementedLoginServiceServer 是否被正確嵌入(通過值而不是指針)。這避免了在方法調用時出現空指針引用。

type LoginServiceServer interface {GetCaptcha(context.Context, *CaptchaMessage) (*CaptchaResponse, error)mustEmbedUnimplementedLoginServiceServer()
}// UnimplementedLoginServiceServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedLoginServiceServer struct{}func (UnimplementedLoginServiceServer) GetCaptcha(context.Context, *CaptchaMessage) (*CaptchaResponse, error) {return nil, status.Errorf(codes.Unimplemented, "method GetCaptcha not implemented")
}
func (UnimplementedLoginServiceServer) mustEmbedUnimplementedLoginServiceServer() {}
func (UnimplementedLoginServiceServer) testEmbeddedByValue()                      {}

所以主要是為了,確保向前兼容性:通過嵌入 UnimplementedLoginServiceServer,服務實現者可以在未來版本中添加新方法,而不會破壞現有實現。

梳理gRPC思路

首先我們實現了gRPC,那么原本的api下面的user相關的我們可以刪除了。

來看看我們實現了什么。

首先main.go中的相關代碼如下。

在這里插入圖片描述
然后在service中我們實現了login_service.go代碼,如下。

package login_service_v1import ("context""errors""fmt""go.uber.org/zap""log"common "test.com/project-common""test.com/project-common/logs""test.com/project-user/pkg/dao""test.com/project-user/pkg/repo""time"
)type LoginService struct {UnimplementedLoginServiceServercache repo.Cache
}func New() *LoginService {return &LoginService{cache: dao.Rc,}
}func (ls LoginService) GetCaptcha(ctx context.Context, msg *CaptchaMessage) (*CaptchaResponse, error) {//1.獲取參數moblie := msg.Mobilefmt.Println(moblie)//2.校驗參數if !common.VerifyMoblie(moblie) {return nil, errors.New("手機號不合法")}//3.生成驗證碼(隨機4位或者6位)code := "123456"//4.調用短信平臺(三方,放入go協程中執行,接口可以快速響應,短信幾秒到無所謂)go func() {time.Sleep(1 * time.Second)zap.L().Info("短信平臺調用成功,發送短信 INFO")logs.LG.Debug("短信平臺調用成功,發送短信 debug")zap.L().Error("短信平臺調用成功,發送短信 error")// redis 假設后續緩存在mysql或者mongo當中,也有可能存儲在別的當中// 所以考慮用接口實現,面向接口編程“低耦合,高內聚“// 5.存儲驗證碼redis,設置過期時間15分鐘即可c, cancel := context.WithTimeout(context.Background(), 2*time.Second)defer cancel()err := ls.cache.Put(c, "REGISTER_"+moblie, code, 15*time.Minute)if err != nil {log.Printf("驗證碼存入redis出錯,causer by :%v\n", err)}log.Printf("將手機號和驗證碼存入redis成功:REGISTER %s : %s", moblie, code)}()return &CaptchaResponse{}, nil
}

并且在router中更新了如下代碼。

package routerimport ("github.com/gin-gonic/gin""google.golang.org/grpc""log""net""test.com/project-user/config"loginServiceV1 "test.com/project-user/pkg/service/login.service.v1"
)// Router 接口
type Router interface {Route(r *gin.Engine)
}type RegisterRouter struct {
}func New() *RegisterRouter {return &RegisterRouter{}
}func (*RegisterRouter) Route(ro Router, r *gin.Engine) {ro.Route(r)
}var routers []Routerfunc InitRouter(r *gin.Engine) {for _, ro := range routers {ro.Route(r)}
}type gRPCConfig struct {Addr         stringRegisterFunc func(*grpc.Server)
}func RegisterGrpc() *grpc.Server {c := gRPCConfig{Addr: config.C.GC.Addr,RegisterFunc: func(g *grpc.Server) {//注冊loginServiceV1.RegisterLoginServiceServer(g, loginServiceV1.New())}}s := grpc.NewServer()c.RegisterFunc(s)lis, err := net.Listen("tcp", c.Addr)if err != nil {log.Println("cannot listen")}//把服務放到協程里邊go func() {err = s.Serve(lis)if err != nil {log.Println("server started error", err)return}}()return s
}

在這里插入圖片描述

好,有點復雜,這里我們畫圖梳理下關系。

首先在router.go文件中,我們聲明了RegisterGrpc(),這是gRPC服務的入口點,主要是配置grpc配置,包括服務地址還有注冊函數,并且創建gRPC實例,然后注冊登錄服務,最后是啟動gRPC服務器(在goroutine中運行的。)

login_service.go 中:LoginService 結構體:實現了 gRPC 服務接口,New() 函數:創建 LoginService 實例,GetCaptcha() 方法:實現具體的驗證碼獲取業務邏輯。

所以調用關系如下。
在這里插入圖片描述
在這里插入圖片描述
所以為什么要使用協程?因為如果不使用協程,s.Serve(lis)會阻塞主線程,導致后續代碼無法繼續運行,這樣可以運行gRPC服務器與HTTP服務器(gin)同時運行。

gRPC可以獨立運行,不影響主程序的其他功能。


優雅關閉gRPC

在main函數中,還有個stop,這是閉包函數,說實話這是第一次看到閉包函數的使用場景,首先我們捕獲了外部變量gc,gc也就是gRPC服務器實例,然后定義了服務關閉的具體行為,也就是停止gRPC服務,作為參數傳給srv.Run。

當Run函數接受一個stop函數作為參數,注釋一種依賴注入的設計模式,當收到指令之后,會把gRPC給關閉了。

雖然 stop 函數被傳入,但它并不會立即執行,代碼會在 <-quit 這行被阻塞。只有當程序收到 SIGINT 或 SIGTERM 信號時(比如按 Ctrl+C),才會繼續往下執行,然后才會檢查 stop != nil 并執行 stop 函數。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/71463.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/71463.shtml
英文地址,請注明出處:http://en.pswp.cn/web/71463.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

深度學習反向傳播

一、白話解釋 梯度其實就是導數&#xff0c;除了用符號求導也可以用近似求導&#xff1a; 然后更新ww-學習率*導數 反向傳播就是鏈式求導 向前計算&#xff1a;對每個節點求偏導 在前向傳播的時候&#xff0c;進行一次前向計算的時候就可以把每一條線的偏導數都知道 前向傳…

JavaWeb-HttpServletRequest請求域接口

文章目錄 HttpServletRequest請求域接口HttpServletRequest請求域接口簡介關于請求域和應用域的區別 請求域接口中的相關方法獲取前端請求參數(getParameter系列方法)存儲請求域名參數(Attribute系列方法)獲取客戶端的相關地址信息獲取項目的根路徑 關于轉發和重定向的細致剖析…

deepseek在pycharm 中的配置和簡單應用

對于最常用的調試python腳本開發環境pycharm&#xff0c;如何接入deepseek是我們窺探ai代碼編寫的第一步&#xff0c;熟悉起來總沒壞處。 1、官網安裝pycharm社區版&#xff08;免費&#xff09;&#xff0c;如果需要安裝專業版&#xff0c;需要另外找破解碼。 2、安裝Ollama…

AAA協議:從零認識網絡的“身份管家”

AAA&#xff08;Authentication, Authorization, Accounting&#xff0c;認證、授權和計費&#xff09;是網絡世界的“身份管理員”&#xff0c;負責確認“你是誰”、決定“你能干啥”、記錄“你干了啥”。如果你用過華三的交換機或路由器&#xff0c;可能在配置用戶管理時見過…

動態規劃01背包問題系列一>最后一塊石頭的重量II

這里寫目錄標題 題目分析&#xff1a;狀態表示&#xff1a;狀態轉移方程&#xff1a;初始化&#xff1a;填表順序&#xff1a;返回值&#xff1a;代碼呈現&#xff1a;優化版本&#xff1a;代碼呈現&#xff1a; 題目分析&#xff1a; 狀態表示&#xff1a; 狀態轉移方程&#…

逐行拆解 C 語言:數據類型、變量

今日&#xff0c;我們即將踏上一段充滿趣味與挑戰的學習之旅&#xff0c;深度鉆研數據類型的多樣奧秘&#xff0c;解鎖變量創建的實用技巧。不僅如此&#xff0c;還會邂逅兩個實用的基礎庫函數&#xff0c;探索它們在程序中穿針引線的奇妙作用。同時&#xff0c;幾個簡潔卻強大…

【音視頻】ffplay簡單過濾器

一、ffplay簡單過濾器 視頻旋轉&#xff1a;借助transpose濾鏡 ffplay -i 1.mp4 -vf transpose1這里選擇不同的數字是不同的方向&#xff1a; 視頻翻轉&#xff1a;借助hflip/vflip實現水平和垂直翻轉&#xff1a; 水平翻轉 ffplay 1.mp4 -vf hflip垂直翻轉 ffplay 1.mp4 …

springboot中注解有什么用

注解&#xff08;Annotation&#xff09;是 Java 的一個重要特性&#xff0c;我用幾個具體例子來解釋&#xff1a; 1、標記功能 Service // 告訴Spring這是一個服務類 public class UserService { }Data // 告訴Lombok自動生成getter/setter public class User {private…

Excel中COUNTIF用法解析

COUNTIF 是 Excel 中一個非常實用的函數&#xff0c;用于統計滿足某個條件的單元格數量。它的基本語法如下&#xff1a; 基本語法 COUNTIF(范圍, 條件) 范圍&#xff1a;需要統計的單元格區域&#xff0c;例如 A1:A10 或整列 A:A。 條件&#xff1a;用于判斷哪些單元格需要被…

java根據List<Object>中的某個屬性排序(數據極少,順序固定)

public static void main(String[] args) { List<HashMap<String, Object>> dydj new ArrayList<>(); // 模擬原始數據 HashMap<String, Object> map1 new HashMap<>(); map1.put(“city_name”, “張家口”); map1.put(“wjs”, 0); map1.put…

4G工業路由器在公交充電樁中的應用與優勢

隨著電動公交車的普及&#xff0c;公交充電樁的穩定運行和高效管理是交通營運部門最關心的問題。4G工業路由器憑借其卓越的數據采集和通訊能力&#xff0c;成為實現充電樁智能化管理的關鍵。 公交充電樁運維管理需求概述&#xff1a; 1.實時性&#xff1a;實時監控充電狀態、剩…

利用golang embed特性嵌入前端資源問題解決

embed嵌入前端資源&#xff0c;配置前端路由的代碼如下 func StartHttpService(port string, assetsFs embed.FS) error {//r : gin.Default()gin.SetMode(gin.ReleaseMode)r : gin.New()r.Use(CORSMiddleware())// 靜態文件服務dist, err : fs.Sub(assetsFs, "assets/di…

【LangChain 數據連接封裝】 文檔加載器、文檔處理器

小結&#xff1a; 文檔處理部分&#xff0c;建議在實際應用中詳細測試后使用與向量數據庫的鏈接部分本質是接口封裝&#xff0c;向量數據庫需要自己選型類似 LlamaIndex&#xff0c;LangChain 也提供了豐富的 Document Loaders DocumentLoaders和 Text Splitters Text Splitte…

Springboot集成dubbo完整過程(三)

準備工作 1&#xff0c;準備mysql服務環境2&#xff0c;準備redis服務環境3&#xff0c;準備zookeeper服務環境4&#xff0c;準備逆向生成bean的xml配置文件5&#xff0c;準備slf4j日志xml配置文件6&#xff0c;準備一個sql腳本 1&#xff0c;搭建創建服務工程 1&#xff0c;創…

【DeepSeek 】學習編程的利器:DeepSeek 使用指南

學習編程的利器&#xff1a;DeepSeek 使用指南 如果你正苦于如何開始學習 Python/R/Linux/HTML 語法&#xff0c;這個方法或許是你學習過程中的利器&#xff0c;又不用考慮請教真人&#xff0c;麻煩別人。 學習階段 第一階段&#xff1a;通讀語法書籍 第一步通讀一些相關語法…

【大模型篇】目前主流 AI 大模型體系全解析:架構、特點與應用

大家好,我是大 F,深耕AI算法十余年,互聯網大廠技術崗。分享AI算法干貨、技術心得。 歡迎關注《大模型理論和實戰》、《DeepSeek技術解析和實戰》,一起探索技術的無限可能! 閱讀完本文,您將知道:目前主流的大模型體系有哪些?及其架構的特點。 前言 在自然語言處理(NL…

電池管理系統(BMS)架構詳細解析:原理與器件選型指南

BMS&#xff08;電池管理系統&#xff09;架構詳細講解 從你提供的BMS&#xff08;Battery Management System&#xff09;架構圖來看&#xff0c;主要涉及到電池監控模塊、通信模塊、功率控制模塊等部分。下面我將詳細講解該架構的各個功能模塊及其工作原理。 1. 電池管理核…

決策樹(Decision Tree)基礎知識

目錄 一、回憶1、*機器學習的三要素&#xff1a;1&#xff09;*函數族2&#xff09;*目標函數2.1&#xff09;*模型的其他復雜度參數 3&#xff09;*優化算法 2、*前處理/后處理1&#xff09;前處理&#xff1a;特征工程2&#xff09;后處理&#xff1a;模型選擇和模型評估 3、…

洛谷 P3648 APIO2014 序列分割 題解

寫了挺多斜率優化的題目了&#xff0c;這道&#xff08;差點&#xff09;就速切了&#xff0c;原因還是單調隊列維護斜率的寫法出鍋。 題意 題目描述 你正在玩一個關于長度為 n n n 的非負整數序列的游戲。這個游戲中你需要把序列分成 k 1 k 1 k1 個非空的塊。為了得到 …

策略模式的C++實現示例

核心思想 策略模式是一種行為型設計模式&#xff0c;它定義了一系列算法&#xff0c;并將每個算法封裝在獨立的類中&#xff0c;使得它們可以互相替換。策略模式讓算法的變化獨立于使用它的客戶端&#xff0c;從而使得客戶端可以根據需要動態切換算法&#xff0c;而不需要修改…