全網最詳細的 gin框架請求數據綁定Bind 源碼解析 -- 幫助你全面了解gin框架的請求數據綁定原理和方法

?在gin框架中,我們可以將多種請求數據(json, form,uri,header等)直接綁定到我們定義的結構體,底層是通過反射方式獲取我們定義在結構體上面的tag來實現請求數據到我們的結構體數據的綁定的。 在gin的底層有2大體系的數據綁定一個是Bind,是個是ShouldBind, 下面我們就從數據綁定入口開始一層層的解開gin數據綁定的神秘面紗!

gin中支持的數據綁定類型

? ? ? ? gin框架中的所有數據的綁定都是通過請求類型的 Content-Type這個 MIME類型來完成的,他所支持的類型如下:

// Content-Type MIME of the most common data formats.
const (MIMEJSON              = "application/json"MIMEHTML              = "text/html"MIMEXML               = "application/xml"MIMEXML2              = "text/xml"MIMEPlain             = "text/plain"MIMEPOSTForm          = "application/x-www-form-urlencoded"MIMEMultipartPOSTForm = "multipart/form-data"MIMEPROTOBUF          = "application/x-protobuf"MIMEMSGPACK           = "application/x-msgpack"MIMEMSGPACK2          = "application/msgpack"MIMEYAML              = "application/x-yaml"MIMEYAML2             = "application/yaml"MIMETOML              = "application/toml"
)

我們在Bind和ShouldBind? 2大序列?中是使用的XXX 定義

// These implement the Binding interface and can be used to bind the data
// present in the request to struct instances.
var (JSON          BindingBody = jsonBinding{}XML           BindingBody = xmlBinding{}Form          Binding     = formBinding{}Query         Binding     = queryBinding{}FormPost      Binding     = formPostBinding{}FormMultipart Binding     = formMultipartBinding{}ProtoBuf      BindingBody = protobufBinding{}MsgPack       BindingBody = msgpackBinding{}YAML          BindingBody = yamlBinding{}Uri           BindingUri  = uriBinding{}Header        Binding     = headerBinding{}TOML          BindingBody = tomlBinding{}
)

上面這些就是gin框架中支持的數據的綁定類型XXX定義, 如 BindJSON,? BindForm,? ShouldBindJSON,? ? ShouldBinxUri? 等。

gin框架中的2大類型的數據綁定方式

????????他們實現的功能是一樣的,區別在于Bind序列如果數據綁定失敗會直接拋異常并退出當前請求,而ShouldBind 則不會中斷當前的請求。?原因是 Bind序列使用的是?c.MustBindWith ,注意這里的名字前綴 Must? 在go語言的開發中我們通常的做法就是帶這個Must的方法,就表示必須要滿足的方法, 如果不滿足就直接給你個?panic 異常(直接退出當前請求),gin框架也不另外MustXxx?的方法也是必須要滿足的,否則panic中斷當前請求; 而ShouldBind序列是通過? c.ShouldBindWith 來實現的,他在數據綁定異常時會忽略異常,繼續后的的請求。

1. ?Bind? 序列 示例

他由Bind方法,和 BindXXX 方法主從,他們內部都是調用了c.MustBindWith 方法,這里的XXX 即gin中支持的數據綁定類型,見 gin中支持的數據綁定類型定義?

func (c *Context) Bind(obj any) error {b := binding.Default(c.Request.Method, c.ContentType())return c.MustBindWith(obj, b)
}// BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON).
func (c *Context) BindJSON(obj any) error {return c.MustBindWith(obj, binding.JSON)
}
// ....

2.? ShouldBind序列 示例

func (c *Context) ShouldBind(obj any) error {b := binding.Default(c.Request.Method, c.ContentType())return c.ShouldBindWith(obj, b)
}// ShouldBindJSON is a shortcut for c.ShouldBindWith(obj, binding.JSON).
func (c *Context) ShouldBindJSON(obj any) error {return c.ShouldBindWith(obj, binding.JSON)
}
// ......

gin 中數據綁定接口定義

????????不管是那個序列的數據綁定,他們都是通過實現以下接口來完成具體的數據綁定的,這個也是go語言的一個核心思想 -- 面向接口編程 !? 你沒有看錯就是面向接口編程,而你常見其他語言,如java 等好像都是說的面向對象編程,而go語言的特別就在于此, go語言中把面向接口編程做到了極致!

? ? ? ? gin框架中為數據綁定定義了3個接口來實現不同類型的數據綁定。?


// Binding describes the interface which needs to be implemented for binding the
// data present in the request such as JSON request body, query parameters or
// the form POST.
type Binding interface {Name() stringBind(*http.Request, any) error
}// BindingBody adds BindBody method to Binding. BindBody is similar with Bind,
// but it reads the body from supplied bytes instead of req.Body.
type BindingBody interface {BindingBindBody([]byte, any) error
}// BindingUri adds BindUri method to Binding. BindUri is similar with Bind,
// but it reads the Params.
type BindingUri interface {Name() stringBindUri(map[string][]string, any) error
}

gin中的數據綁定實現

? ? ? ? gin框架中已經給我們實現了多種常見的數據類型的綁定,見?gin中支持的數據綁定類型 。 當然, 如果已有實現中沒有你想要的數據類型的綁定或者你想自己動手來實現, 這個也非常簡單, 你只要實現上面定義的對應的接口即可! 不知道怎么實現的話你就參考一下gin中已有的實現,哈哈!

gin框架數據綁定實現截圖

form數據綁定實現示例

????????這里的數據實現比較多, 我們就以 我們最常用的form數據綁定實現為例,和大家一起來學習一下gin中的數據綁定是如何實現的。

1.? 數據綁定入口

????????下面的formBinding 綁定是普通form的綁定, 另外還有formPostBinding? POST類型的數據綁定,?formMultipartBinding 這個是針對媒體上傳類型的數據的綁定實現,我們就不一一列舉了,他們的實現思路都差不多。


func (formBinding) Bind(req *http.Request, obj any) error {if err := req.ParseForm(); err != nil {return err}if err := req.ParseMultipartForm(defaultMemory); err != nil && !errors.Is(err, http.ErrNotMultipart) {return err}if err := mapForm(obj, req.Form); err != nil {return err}return validate(obj)
}
2.? 請求form數據解析?req.ParseForm()?

// ParseForm populates r.Form and r.PostForm.
//
// For all requests, ParseForm parses the raw query from the URL and updates
// r.Form.
//
// For POST, PUT, and PATCH requests, it also reads the request body, parses it
// as a form and puts the results into both r.PostForm and r.Form. Request body
// parameters take precedence over URL query string values in r.Form.
//
// If the request Body's size has not already been limited by [MaxBytesReader],
// the size is capped at 10MB.
//
// For other HTTP methods, or when the Content-Type is not
// application/x-www-form-urlencoded, the request Body is not read, and
// r.PostForm is initialized to a non-nil, empty value.
//
// [Request.ParseMultipartForm] calls ParseForm automatically.
// ParseForm is idempotent.
func (r *Request) ParseForm() error {var err errorif r.PostForm == nil {if r.Method == "POST" || r.Method == "PUT" || r.Method == "PATCH" {r.PostForm, err = parsePostForm(r)}if r.PostForm == nil {r.PostForm = make(url.Values)}}if r.Form == nil {if len(r.PostForm) > 0 {r.Form = make(url.Values)copyValues(r.Form, r.PostForm)}var newValues url.Valuesif r.URL != nil {var e errornewValues, e = url.ParseQuery(r.URL.RawQuery)if err == nil {err = e}}if newValues == nil {newValues = make(url.Values)}if r.Form == nil {r.Form = newValues} else {copyValues(r.Form, newValues)}}return err
}
3. 上傳類型數據解析?req.ParseMultipartForm

// ParseMultipartForm parses a request body as multipart/form-data.
// The whole request body is parsed and up to a total of maxMemory bytes of
// its file parts are stored in memory, with the remainder stored on
// disk in temporary files.
// ParseMultipartForm calls [Request.ParseForm] if necessary.
// If ParseForm returns an error, ParseMultipartForm returns it but also
// continues parsing the request body.
// After one call to ParseMultipartForm, subsequent calls have no effect.
func (r *Request) ParseMultipartForm(maxMemory int64) error {if r.MultipartForm == multipartByReader {return errors.New("http: multipart handled by MultipartReader")}var parseFormErr errorif r.Form == nil {// Let errors in ParseForm fall through, and just// return it at the end.parseFormErr = r.ParseForm()}if r.MultipartForm != nil {return nil}mr, err := r.multipartReader(false)if err != nil {return err}f, err := mr.ReadForm(maxMemory)if err != nil {return err}if r.PostForm == nil {r.PostForm = make(url.Values)}for k, v := range f.Value {r.Form[k] = append(r.Form[k], v...)// r.PostForm should also be populated. See Issue 9305.r.PostForm[k] = append(r.PostForm[k], v...)}r.MultipartForm = freturn parseFormErr
}
4. 數據映射 函數?mapForm ,?mapFormByTag

? ?注意這里是一個函數,上面2個ParseForm 和?ParseMultipartForm 都是在請求對象上面的方法。

從下面的代碼可見, 他這里調用的是mapFormByTag 這個函數,這個即是根據我們定義在結構體中的Tag來映射數據, 這里因為是form類型的數據綁定,所以這個地方的第三個參數就是 form

func mapForm(ptr any, form map[string][]string) error {return mapFormByTag(ptr, form, "form")
}

我們接著看看這個mapFormByTag

這里的ptr就是我們要將數據綁定到的我們自定義的結構體對象的指針, form 這個就是上面解析后的請求表單的數據map,? ?第三個參數 tag 這個就是我們要解析的數據類型的Tag定義名稱,這里就的?form 就表示他解析的數據就是我們的結構體TAG中的名稱為form的Tag數據, 如我們結構體中的字段Page的定義??Page int `json:"page" form:"page" `??, 這里的tag名稱就是form,而對于的字段名稱就是 page, 就表示他可以綁定請求參數page的值到 結構體的 Page 字段。


func mapFormByTag(ptr any, form map[string][]string, tag string) error {// Check if ptr is a mapptrVal := reflect.ValueOf(ptr)var pointed anyif ptrVal.Kind() == reflect.Ptr {ptrVal = ptrVal.Elem()pointed = ptrVal.Interface()}if ptrVal.Kind() == reflect.Map &&ptrVal.Type().Key().Kind() == reflect.String {if pointed != nil {ptr = pointed}return setFormMap(ptr, form)}return mappingByPtr(ptr, formSource(form), tag)
}

PS: 這里有一個很容易忽略但又非常重要的知識點,就是Elem這個方法的應用時機。 當我們在對一個對象應用函數 reflect.ValueOf() 獲取對于的 reflect.Value 對象后, 如果any類型的入參 ptr是一個指針,則獲取到的Value對象就必須要調用 .Elem()方法獲取指針對應的具體的數據的 reflect.Value后再進行操作,否則就獲取不到你想要的數據,因為你拜佛沒有找對廟門,哈哈!

5. mappingByPtr函數

這個就是具體的數據綁定映射函數的實現邏輯了, 他這個里面用了遞歸方式來處理數據的映射,另外還使用了一個 setter 數據設置接口來進行數據的設置。?

從下面的代碼我們可以看到,在mapping的第一行就繼續了一個tag名稱的判斷,如果名稱是 - 就直接返回 忽略了這個字段的映射處理。? 如我們結構體中某個字段的tag 名稱是是這樣定義的? Name string `form:"-" `? 這個就表示會忽略Name這個字段的form數據的綁定


func mappingByPtr(ptr any, setter setter, tag string) error {_, err := mapping(reflect.ValueOf(ptr), emptyField, setter, tag)return err
}func mapping(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) {if field.Tag.Get(tag) == "-" { // just ignoring this fieldreturn false, nil}vKind := value.Kind()if vKind == reflect.Ptr {var isNew boolvPtr := valueif value.IsNil() {isNew = truevPtr = reflect.New(value.Type().Elem())}isSet, err := mapping(vPtr.Elem(), field, setter, tag)if err != nil {return false, err}if isNew && isSet {value.Set(vPtr)}return isSet, nil}if vKind != reflect.Struct || !field.Anonymous {ok, err := tryToSetValue(value, field, setter, tag)if err != nil {return false, err}if ok {return true, nil}}if vKind == reflect.Struct {tValue := value.Type()var isSet boolfor i := 0; i < value.NumField(); i++ {sf := tValue.Field(i)if sf.PkgPath != "" && !sf.Anonymous { // unexportedcontinue}ok, err := mapping(value.Field(i), sf, setter, tag)if err != nil {return false, err}isSet = isSet || ok}return isSet, nil}return false, nil
}
6 setter數據設置接口定義

這個接口就定義了一個方法, TrySet 嘗試幫我們設置數據

// setter tries to set value on a walking by fields of a struct
type setter interface {TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSet bool, err error)
}
7. 數據設置函數?tryToSetValue?

這里就是數據映射過程中的字段Tag值的獲取核心函數。 通過下面的代碼我們可以找到gin的數據綁定的Tag中的數據是如何處理的。? ? 詳見下面的代碼注釋


func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) {var tagValue stringvar setOpt setOptions// 通過反射獲取結構體字段tag對應的數據,tagValue = field.Tag.Get(tag)//將獲取到的tag數據再使用逗號分隔?tagValue, opts := head(tagValue, ",")if tagValue == "" { // default value is FieldNametagValue = field.Name}if tagValue == "" { // when field is "emptyField" variablereturn false, nil}var opt stringfor len(opts) > 0 {opt, opts = head(opts, ",")// 如果獲取到的tag值中包含了 default=xx  則對這個字段設置默認值if k, v := head(opt, "="); k == "default" {setOpt.isDefaultExists = truesetOpt.defaultValue = v}}return setter.TrySet(value, field, tagValue, setOpt)
}

? ? ? ? 根據上面的代碼 舉例說明:? field.Tag.Get(tag)? 這個就是獲取我們在結構體中設置的tag對應的值, 如 假設tag為form, 我們有一個結構體中的字段定義是? Page int `json:"page" form:"page,default=1" `? ? ?這里的代碼field.Tag.Get(tag)? 獲取到的內容就是?page,default=1

tagValue, opts := head(tagValue, ",") 這個獲取到的是tagValue就是?page, opts的值就是default=1

這個定義的意思就是 將請求表單中的 page 對應的字段幫我們綁定到我們定義的這個結構體的 Page字段上面,如果請求表單中沒有相關的數據則使用這里定義的默認值1(default=1就是定義默認值), 這個地方就是如何給綁定數據設置默認值的方式, 這個知識點gin官方文檔和示例可沒有哦!! 這個就是通過這里的源碼發現的使用方法。

form的setter接口執行?TrySet
// TrySet tries to set a value by request's form source (like map[string][]string)
func (form formSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSet bool, err error) {return setByForm(value, field, form, tagValue, opt)
}
?form數據設置函數?setByForm

通過下面的代碼,可見他可以設置的數據類型有 切片, 數組,還有可序列化的數據(默認),這個可序列化的數據類型就包含所有的可以被序列化的數據。


func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSet bool, err error) {vs, ok := form[tagValue]if !ok && !opt.isDefaultExists {return false, nil}switch value.Kind() {case reflect.Slice:if !ok {vs = []string{opt.defaultValue}}return true, setSlice(vs, value, field)case reflect.Array:if !ok {vs = []string{opt.defaultValue}}if len(vs) != value.Len() {return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String())}return true, setArray(vs, value, field)default:var val stringif !ok {val = opt.defaultValue}if len(vs) > 0 {val = vs[0]}if ok, err := trySetCustom(val, value); ok {return ok, err}return true, setWithProperType(val, value, field)}
}
可序列化的數據 設置
// trySetCustom tries to set a custom type value
// If the value implements the BindUnmarshaler interface, it will be used to set the value, we will return `true`
// to skip the default value setting.
func trySetCustom(val string, value reflect.Value) (isSet bool, err error) {switch v := value.Addr().Interface().(type) {case BindUnmarshaler:return true, v.UnmarshalParam(val)}return false, nil
}

?這個就是可序列化的數據的設置的具體邏輯,這里也是根據反射方式先獲取要設置的結構體的字段的類型,然后根據不同的類型來設置具體的值。? 細心的你應該能夠注意到,我們上面提到的小知識點 指針類型的數據需要先調用 .Elem()方法 的應用,見下面的?case reflect.Ptr:??


func setWithProperType(val string, value reflect.Value, field reflect.StructField) error {switch value.Kind() {case reflect.Int:return setIntField(val, 0, value)case reflect.Int8:return setIntField(val, 8, value)case reflect.Int16:return setIntField(val, 16, value)case reflect.Int32:return setIntField(val, 32, value)case reflect.Int64:switch value.Interface().(type) {case time.Duration:return setTimeDuration(val, value)}return setIntField(val, 64, value)case reflect.Uint:return setUintField(val, 0, value)case reflect.Uint8:return setUintField(val, 8, value)case reflect.Uint16:return setUintField(val, 16, value)case reflect.Uint32:return setUintField(val, 32, value)case reflect.Uint64:return setUintField(val, 64, value)case reflect.Bool:return setBoolField(val, value)case reflect.Float32:return setFloatField(val, 32, value)case reflect.Float64:return setFloatField(val, 64, value)case reflect.String:value.SetString(val)case reflect.Struct:switch value.Interface().(type) {case time.Time:return setTimeField(val, field, value)case multipart.FileHeader:return nil}return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())case reflect.Map:return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())case reflect.Ptr:if !value.Elem().IsValid() {value.Set(reflect.New(value.Type().Elem()))}return setWithProperType(val, value.Elem(), field)default:return errUnknownType}return nil
}

ok, 至此,gin框架中的數據請求綁定源碼都扒完了...? ?后面就是如何使用了,當你了解了他的原理后使用那就是小菜一碟了, 本文就不做討論了。。。。。。

如果本文對你有幫助,歡迎點贊,收藏,評論, 你的支持就是我們繼續產出優質內容的動力哦 :)?

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

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

相關文章

Python pip install模塊時C++編譯環境問題

pip install模塊時C編譯環境問題 在接觸和使用python后&#xff0c;常常會通過pip install命令安裝第三方模塊&#xff0c;大多數模塊可以直接安裝&#xff0c;但許多新同學仍會遇見某些模塊需要實時編譯后才能安裝&#xff0c;如報錯信息大概是缺乏C編譯環境&#xff0c;本文則…

【Elasticsearch】Elasticsearch索引創建與管理詳解

文章目錄 &#x1f4d1;引言一、Elasticsearch 索引的基礎概念二、創建索引2.1 使用默認設置創建索引2.2 自定義設置創建索引2.3 創建索引并設置映射 三、索引模板3.1 創建索引模板3.2 使用索引模板創建索引 四、管理索引4.1 查看索引4.2 更新索引設置4.3 刪除索引 五、索引別名…

Go-知識測試-性能測試

Go-知識測試-性能測試 1. 定義2. 例子3. testing.common 測試基礎數據4. testing.TB 接口5. 關鍵函數5.1 testing.runBenchmarks5.2 testing.B.runN5.3 testing.B.StartTimer5.4 testing.B.StopTimer5.5 testing.B.ResetTimer5.6 testing.B.Run5.7 testing.B.run15.8 testing.B…

監聽藍牙對話的BlueSpy技術復現

本文是之前文章的BlueSpy技術的復現過程&#xff1a;https://mp.weixin.qq.com/s/iCeImLLPAwwKH1avLmqEpA 2個月前&#xff0c;網絡安全和情報公司Tarlogic在西班牙安全大會RootedCon 2024上提出了一項利用藍牙漏洞的BlueSpy技術&#xff0c;并在之后發布了一個名為BlueSpy的概…

深度學習之生成對抗網絡StyleGAN3

StyleGAN3 是由 NVIDIA 團隊提出的第三代生成對抗網絡(GAN),在前代 StyleGAN 和 StyleGAN2 的基礎上進行了改進,以實現更高質量的圖像生成。StyleGAN3 的主要改進在于解決了 StyleGAN2 中存在的偽影(artifacts)問題,并且提升了生成圖像的一致性和穩定性。 StyleGAN3 的…

git 提交代碼忽略eslint代碼檢測

在暫存代碼的時候會出現以上情況因為在提交代碼的時候會默認運行代碼進行檢測&#xff0c;如果不符合代碼規范就會進行報錯 解決&#xff1a; 使用 git commit --no-verify -m xxx 忽略eslint的檢測

Laravel 謹慎使用Storage::append()

在 driver 為 local 時&#xff0c;Storage::append()在高并發下&#xff0c;會存在丟失數據問題&#xff0c;文件被覆寫&#xff0c;而非尾部添加&#xff0c;如果明確是本地文件操作&#xff0c;像日志寫入&#xff0c;建議使用 Illuminate\Filesystem\Filesystem或者php原生…

技術成神之路:設計模式(一)單例模式

在軟件設計中&#xff0c;有時我們希望某個類的實例始終是唯一的&#xff0c;即無論在何處訪問這個類&#xff0c;都能夠得到同一個實例。單例模式&#xff08;Singleton Pattern&#xff09;就是為了解決這個問題而產生的。單例模式確保一個類只有一個實例&#xff0c;并提供一…

整合web-socket的常見bug

整合文章連接 此文是記錄我上網查找整合方案時候踩的坑,特別是注冊失敗的問題,比如還有什么去掉Compoent就可以,但是這樣這個端點就失效了 特別是報錯: at org.springframework.web.socket.server.standard.ServerEndpointExporter.registerEndpoint(ServerEndpointExporter.…

大模型日報 2024-06-30

大模型日報 2024-06-30 大模型產品 Briefy: AI知識助手 摘要: Briefy是一款AI知識助手&#xff0c;為專業用戶簡化每日信息消費&#xff0c;將復雜信息提煉成結構化摘要&#xff0c;組織成知識庫&#xff0c;并以自然語言按需檢索。 Claude Projects&#xff1a;組織聊天與共享…

邀請函 | 極限科技全新搜索引擎 INFINI Pizza 亮相 2024 可信數據庫發展大會!

過去一年&#xff0c;在全球 AI 浪潮和國家數據局成立的推動下&#xff0c;數據庫產業變革不斷、熱鬧非凡。2024 年&#xff0c;站在中國數字經濟產業升級和數據要素市場化建設的時代交匯點上&#xff0c;“2024 可信數據庫發展大會” 將于 2024 年 7 月 16-17 日在北京悠唐皇冠…

肆拾玖坊的商業模式,49坊新零售獎金制度體系,眾籌眾創+會員制

肆拾玖坊之所以能夠在短時間內成為白酒行業的“現象級”企業,,不僅是依靠獨特商業模式,同時也依靠的是堅持用戶為核心,圍繞用戶需求,讓用戶與產品直接產生連接理念。 坐標&#xff1a;廈門&#xff0c;我是易創客肖琳 深耕社交新零售行業10年&#xff0c;主要提供新零售系統工…

前端技術(二)——javasctipt 介紹

一、javascript基礎 1. javascript簡介 ⑴ javascript的起源 ⑵ javascript 簡史 ⑶ javascript發展的時間線 ⑷ javascript的實現 ⑸ js第一個代碼 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>…

Vue中的axios深度探索:從基礎安裝到高級功能應用的全面指南

文章目錄 前言一、axios 請求1. axios的概念2. axios的安裝3. axiso請求方式介紹4. axios請求本地數據5. axios跨域6. axios全局注冊7. axios支持的請求類型1&#xff09;get請求2&#xff09;post請求3&#xff09;put請求4&#xff09;patch請求5&#xff09;delete請求 二、…

MyBatis操作數據庫(入門)

本節目標 使用MyBatis完成簡單的增刪改查操作&#xff0c;參數傳遞掌握MyBatis的兩種寫法&#xff1a;注解和XML方式掌握MyBatis相關的日志配置 前言 在應用分層學習中&#xff0c;我們了解web應用程序一般分為三層&#xff0c;即Controller、Service、Dao。在之前的案例中&a…

化學SCI期刊,中科院4區,易錄用,幾乎不退稿

一、期刊名稱 Chemical Papers 二、期刊簡介概況 期刊類型&#xff1a;SCI 學科領域&#xff1a;化學 影響因子&#xff1a;2.1 中科院分區&#xff1a;4區 三、期刊征稿范圍 該雜志致力于基礎和應用化學和化學工程研究。它的范圍很廣&#xff0c;涵蓋了所有化學科學&…

2024年江蘇智能制造工廠名單:我看出了未來擇業和跳槽方向

導語 大家好&#xff0c;我是社長&#xff0c;老K。專注分享智能制造和智能倉儲物流等內容。 新書《智能物流系統構成與技術實踐》 在當今這個飛速發展的時代&#xff0c;智能制造已成為推動工業進步的強大引擎。隨著技術革新的浪潮一波接一波地涌來&#xff0c;我們不禁要問&a…

動手學深度學習(Pytorch版)代碼實踐 -計算機視覺-49風格遷移

49風格遷移 讀入內容圖像&#xff1a; import torch import torchvision from torch import nn import matplotlib.pylab as plt import liliPytorch as lp from d2l import torch as d2l# 讀取內容圖像 content_img d2l.Image.open(../limuPytorch/images/rainier.jpg) plt.…

使用 Swift 遞歸搜索目錄中文件的內容,同時支持 Glob 模式和正則表達式

文章目錄 前言項目設置查找文件讀取CODEOWNERS文件解析規則搜索匹配的文件確定文件所有者輸出結果總結前言 如果你新加入一個團隊,想要快速的了解團隊的領域和團隊中擁有的代碼庫的詳細信息。 如果新團隊中的代碼庫在 GitHub / GitLab 中并且你不熟悉代碼所有權模型的概念或…

Unity開箱即用的UGUI面板的拖拽移動功能

文章目錄 &#x1f449;一、背景&#x1f449;二、效果圖&#x1f449;三、原理&#x1f449;四、核心代碼&#x1f449;五&#xff0c;總結 &#x1f449;一、背景 之前做PC項目時常常有面板拖拽移動的需求&#xff0c;今天總結封裝一下&#xff0c;做成一個隨時隨地可復用的…