首页 > 其他分享 >基于DDD的golang实现

基于DDD的golang实现

时间:2023-03-07 19:44:52浏览次数:37  
标签:基于 return err nil user golang User string DDD

女主宣言

今天小编为大家分享基于DDD的golang实现,DDD即领域驱动设计,该模式也算是比较热门的话题了。希望通过本篇文章,大家能够掌握DDD模式,能对大家有所帮助。

PS:丰富的一线技术、多元化的表现形式,尽在“360云计算”,点关注哦!

领域驱动设计模式算是比较热门的话题了。

领域驱动设计(DDD)是一种软件开发方法,通过将实现与不断演变的模型相连接,简化了开发人员面临的复杂性。

本文不会重点去解释Golang中实现DDD的相关理念,而是作者根据自己的研究对DDD的理解。

 

<iframe data-google-container-id="a!3" data-google-query-id="CKTMucnJyf0CFcqAwgodHRwKag" data-load-complete="true" frameborder="0" height="0" id="aswift_2" marginheight="0" marginwidth="0" name="aswift_2" scrolling="no" src="https://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-1776224780566592&output=html&h=187&slotname=3590016480&adk=2457033027&adf=3112199602&pi=t.ma~as.3590016480&w=748&fwrn=4&lmt=1678183719&rafmt=11&format=748x187&url=https%3A%2F%2Fwww.codenong.com%2Fcs106393739%2F&wgl=1&uach=WyJXaW5kb3dzIiwiMTAuMC4wIiwieDg2IiwiIiwiMTEwLjAuNTQ4MS4xNzgiLFtdLGZhbHNlLG51bGwsIjY0IixbWyJDaHJvbWl1bSIsIjExMC4wLjU0ODEuMTc4Il0sWyJOb3QgQShCcmFuZCIsIjI0LjAuMC4wIl0sWyJHb29nbGUgQ2hyb21lIiwiMTEwLjAuNTQ4MS4xNzgiXV0sZmFsc2Vd&dt=1678183719472&bpp=2&bdt=24330&idt=242&shv=r20230302&mjsv=m202302280101&ptt=9&saldr=aa&abxe=1&cookie=ID%3D2bdf99e63f07b66e-224a384a45d90002%3AT%3D1673698061%3ART%3D1673698061%3AS%3DALNI_MbwJvQyG1sryNJwBud3kK5snBX0KA&gpic=UID%3D00000ba423913de7%3AT%3D1673698061%3ART%3D1677589154%3AS%3DALNI_MaA7v5cjD2Om8hdosHpiYkUWb1hJw&prev_fmts=0x0%2C748x280&nras=1&correlator=401643008406&rume=1&frm=20&pv=1&ga_vid=610842400.1673698061&ga_sid=1678183720&ga_hid=79198192&ga_fc=1&rplot=4&u_tz=480&u_his=1&u_h=1440&u_w=2560&u_ah=1400&u_aw=2560&u_cd=24&u_sd=1&dmc=8&adx=748&ady=851&biw=2543&bih=1297&scr_x=0&scr_y=0&eid=44759842%2C44773809%2C44777877%2C44759876%2C44759927%2C31072742%2C31061691%2C31061693&oid=2&pvsid=3815150298831894&tmod=1737639724&uas=0&nvt=1&ref=https%3A%2F%2Fwww.baidu.com%2Flink%3Furl%3DoyBEXlKRSHCzclTRidyDciVC-sb-b8eFgCpYczGrb2yeTgj1YaQnCGljds_XK7Jj%26wd%3D%26eqid%3D9961a2330000936f0000000664070cf6&fc=1920&brdim=0%2C0%2C0%2C0%2C2560%2C0%2C0%2C0%2C2560%2C1297&vis=2&rsz=%7C%7CEer%7C&abl=CS&pfx=0&fu=128&bc=31&ifi=3&uci=a!3&fsb=1&xpc=ANawAGQMhO&p=https%3A//www.codenong.com&dtd=245" width="748"></iframe>

 

什么是DDD?

以下是考虑使用DDD的原因:

  • 提供解决困难问题的原则和模式

  • 将复杂的设计基于领域模型

  • 在技术和领域专家之间发起创造性的协作,以迭代地完善解决领域问题的概念模型。

 

<iframe data-google-container-id="a!4" data-google-query-id="CMv6ucnJyf0CFUVQYAod63oHYg" data-load-complete="true" frameborder="0" height="0" id="aswift_3" marginheight="0" marginwidth="0" name="aswift_3" scrolling="no" src="https://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-1776224780566592&output=html&h=280&slotname=9509745083&adk=3746457956&adf=1347236672&pi=t.ma~as.9509745083&w=748&fwrn=4&fwrnh=100&lmt=1678183719&rafmt=1&format=748x280&url=https%3A%2F%2Fwww.codenong.com%2Fcs106393739%2F&fwr=0&fwrattr=true&rpe=1&resp_fmts=3&wgl=1&uach=WyJXaW5kb3dzIiwiMTAuMC4wIiwieDg2IiwiIiwiMTEwLjAuNTQ4MS4xNzgiLFtdLGZhbHNlLG51bGwsIjY0IixbWyJDaHJvbWl1bSIsIjExMC4wLjU0ODEuMTc4Il0sWyJOb3QgQShCcmFuZCIsIjI0LjAuMC4wIl0sWyJHb29nbGUgQ2hyb21lIiwiMTEwLjAuNTQ4MS4xNzgiXV0sZmFsc2Vd&dt=1678183719474&bpp=1&bdt=24331&idt=245&shv=r20230302&mjsv=m202302280101&ptt=9&saldr=aa&abxe=1&cookie=ID%3D2bdf99e63f07b66e-224a384a45d90002%3AT%3D1673698061%3ART%3D1673698061%3AS%3DALNI_MbwJvQyG1sryNJwBud3kK5snBX0KA&gpic=UID%3D00000ba423913de7%3AT%3D1673698061%3ART%3D1677589154%3AS%3DALNI_MaA7v5cjD2Om8hdosHpiYkUWb1hJw&prev_fmts=0x0%2C748x280%2C748x187&nras=1&correlator=401643008406&rume=1&frm=20&pv=1&ga_vid=610842400.1673698061&ga_sid=1678183720&ga_hid=79198192&ga_fc=1&u_tz=480&u_his=1&u_h=1440&u_w=2560&u_ah=1400&u_aw=2560&u_cd=24&u_sd=1&dmc=8&adx=748&ady=1261&biw=2543&bih=1297&scr_x=0&scr_y=0&eid=44759842%2C44773809%2C44777877%2C44759876%2C44759927%2C31072742%2C31061691%2C31061693&oid=2&pvsid=3815150298831894&tmod=1737639724&uas=0&nvt=1&ref=https%3A%2F%2Fwww.baidu.com%2Flink%3Furl%3DoyBEXlKRSHCzclTRidyDciVC-sb-b8eFgCpYczGrb2yeTgj1YaQnCGljds_XK7Jj%26wd%3D%26eqid%3D9961a2330000936f0000000664070cf6&fc=1920&brdim=0%2C0%2C0%2C0%2C2560%2C0%2C0%2C0%2C2560%2C1297&vis=2&rsz=%7C%7CEer%7C&abl=CS&pfx=0&fu=128&bc=31&ifi=4&uci=a!4&fsb=1&xpc=AY8lTljsGj&p=https%3A//www.codenong.com&dtd=248" width="748"></iframe>

 

DDD包含4个层:

  1. Domain:这是定义应用程序的域和业务逻辑的地方

  2. Infrastructure:此层包含独立于我们的应用程序而存在的所有内容:外部库,数据库引擎等。

  3. Application:该层用作域和界面层之间的通道。将请求从接口层发送到域层,由域层处理请求并返回响应。

  4. Interface:该层包含与其他系统交互的所有内容,例如Web服务,RMI接口或Web应用程序以及批处理前端。

1

开始

 

 

我们将构建一个食物推荐API。

首先要做的是初始化依赖关系管理。我们将使用go.mod。在根目录(路径:food-app /)中,初始化go.mod:

1 go mod init food-app

项目的组织结构:

在该应用中,我们将使用postgres和redis数据库持久化数据。先定义一个含有连接信息的.env文件。
.env文件内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#Postgres
APP_ENV=local
API_PORT=8888
DB_HOST=127.0.0.1
DB_DRIVER=postgres
ACCESS_SECRET=98hbun98h
REFRESH_SECRET=786dfdbjhsb
DB_USER=steven
DB_PASSWORD=password
DB_NAME=food-app
DB_PORT=5432

#Mysql
#DB_HOST=127.0.0.1
#DB_DRIVER=mysql
#DB_USER=steven
#DB_PASSWORD=here
#DB_NAME=food-app
#DB_PORT=3306


#Postgres Test DB
TEST_DB_DRIVER=postgres
TEST_DB_HOST=127.0.0.1
TEST_DB_PASSWORD=password
TEST_DB_USER=steven
TEST_DB_NAME=food-app-test
TEST_DB_PORT=5432

#Redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=

该文件应位于根目录中(路径:food-app /)

2

Domain 层

我们将首先考虑领域。

该域具有几种模式。其中一些是:实体,值,存储库,服务等。

由于我们在此处构建的应用比较简单,因此我们仅考虑两种域模式:实体和存储库。

实体

这是我们定义“Schema”的地方。
例如,我们可以定义用户的结构。将该实体视为域的蓝图。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package entity

import (
    "food-app/infrastructure/security"
    "github.com/badoux/checkmail"
    "html"
    "strings"
    "time"
)

type User struct {
    ID        uint64     `gorm:"primary_key;auto_increment" json:"id"`
    FirstName string     `gorm:"size:100;not null;" json:"first_name"` <iframe data-google-container-id="a!8" data-google-query-id="CNzdg-7Jyf0CFUh3YAodutoJYA" data-load-complete="true" frameborder="0" height="0" id="aswift_7" marginheight="0" marginwidth="0" name="aswift_7" scrolling="no" src="https://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-1776224780566592&output=html&h=280&adk=1974660515&adf=3471602910&pi=t.aa~a.2871807566~i.24~rp.1&w=759&fwrn=4&fwrnh=100&lmt=1678183807&num_ads=1&rafmt=1&armr=3&sem=mc&pwprc=2168828574&ad_type=text_image&format=759x280&url=https%3A%2F%2Fwww.codenong.com%2Fcs106393739%2F&fwr=0&pra=3&rh=190&rw=759&rpe=1&resp_fmts=3&wgl=1&fa=27&uach=WyJXaW5kb3dzIiwiMTAuMC4wIiwieDg2IiwiIiwiMTEwLjAuNTQ4MS4xNzgiLFtdLGZhbHNlLG51bGwsIjY0IixbWyJDaHJvbWl1bSIsIjExMC4wLjU0ODEuMTc4Il0sWyJOb3QgQShCcmFuZCIsIjI0LjAuMC4wIl0sWyJHb29nbGUgQ2hyb21lIiwiMTEwLjAuNTQ4MS4xNzgiXV0sZmFsc2Vd&dt=1678183731354&bpp=5&bdt=36212&idt=5&shv=r20230302&mjsv=m202302280101&ptt=9&saldr=aa&abxe=1&cookie=ID%3D2bdf99e63f07b66e-224a384a45d90002%3AT%3D1673698061%3ART%3D1673698061%3AS%3DALNI_MbwJvQyG1sryNJwBud3kK5snBX0KA&gpic=UID%3D00000ba423913de7%3AT%3D1673698061%3ART%3D1678183731%3AS%3DALNI_MaA7v5cjD2Om8hdosHpiYkUWb1hJw&prev_fmts=0x0%2C748x280%2C748x187%2C748x280%2C257x600%2C257x600&nras=2&correlator=401643008406&rume=1&frm=20&pv=1&ga_vid=610842400.1673698061&ga_sid=1678183720&ga_hid=79198192&ga_fc=1&u_tz=480&u_his=1&u_h=1440&u_w=2560&u_ah=1400&u_aw=2560&u_cd=24&u_sd=1&dmc=8&adx=792&ady=3711&biw=2543&bih=1297&scr_x=0&scr_y=0&eid=44759842%2C44773809%2C44777877%2C44759876%2C44759927%2C31072742%2C31061691%2C31061693&oid=2&pvsid=3815150298831894&tmod=1737639724&uas=0&nvt=1&ref=https%3A%2F%2Fwww.baidu.com%2Flink%3Furl%3DoyBEXlKRSHCzclTRidyDciVC-sb-b8eFgCpYczGrb2yeTgj1YaQnCGljds_XK7Jj%26wd%3D%26eqid%3D9961a2330000936f0000000664070cf6&fc=1408&brdim=0%2C0%2C0%2C0%2C2560%2C0%2C0%2C0%2C2560%2C1297&vis=1&rsz=%7C%7Cs%7C&abl=NS&fu=128&bc=31&ifi=8&uci=a!8&btvi=1&fsb=1&xpc=EggNH3aOud&p=https%3A//www.codenong.com&dtd=76006" width="759"></iframe>
    LastName  string     `gorm:"size:100;not null;" json:"last_name"` <iframe data-google-container-id="a!9" data-google-query-id="CPneg-7Jyf0CFQwIYAodyZ0LMw" data-load-complete="true" frameborder="0" height="0" id="aswift_8" marginheight="0" marginwidth="0" name="aswift_8" scrolling="no" src="https://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-1776224780566592&output=html&h=280&adk=1974660515&adf=2430145915&pi=t.aa~a.2871807566~i.26~rp.1&w=759&fwrn=4&fwrnh=100&lmt=1678183807&num_ads=1&rafmt=1&armr=3&sem=mc&pwprc=2168828574&ad_type=text_image&format=759x280&url=https%3A%2F%2Fwww.codenong.com%2Fcs106393739%2F&fwr=0&pra=3&rh=190&rw=759&rpe=1&resp_fmts=3&wgl=1&fa=27&uach=WyJXaW5kb3dzIiwiMTAuMC4wIiwieDg2IiwiIiwiMTEwLjAuNTQ4MS4xNzgiLFtdLGZhbHNlLG51bGwsIjY0IixbWyJDaHJvbWl1bSIsIjExMC4wLjU0ODEuMTc4Il0sWyJOb3QgQShCcmFuZCIsIjI0LjAuMC4wIl0sWyJHb29nbGUgQ2hyb21lIiwiMTEwLjAuNTQ4MS4xNzgiXV0sZmFsc2Vd&dt=1678183731370&bpp=4&bdt=36227&idt=4&shv=r20230302&mjsv=m202302280101&ptt=9&saldr=aa&abxe=1&cookie=ID%3D2bdf99e63f07b66e-224a384a45d90002%3AT%3D1673698061%3ART%3D1673698061%3AS%3DALNI_MbwJvQyG1sryNJwBud3kK5snBX0KA&gpic=UID%3D00000ba423913de7%3AT%3D1673698061%3ART%3D1678183731%3AS%3DALNI_MaA7v5cjD2Om8hdosHpiYkUWb1hJw&prev_fmts=0x0%2C748x280%2C748x187%2C748x280%2C257x600%2C257x600%2C759x280&nras=3&correlator=401643008406&rume=1&frm=20&pv=1&ga_vid=610842400.1673698061&ga_sid=1678183720&ga_hid=79198192&ga_fc=1&u_tz=480&u_his=1&u_h=1440&u_w=2560&u_ah=1400&u_aw=2560&u_cd=24&u_sd=1&dmc=8&adx=792&ady=4029&biw=2543&bih=1297&scr_x=0&scr_y=0&eid=44759842%2C44773809%2C44777877%2C44759876%2C44759927%2C31072742%2C31061691%2C31061693&oid=2&pvsid=3815150298831894&tmod=1737639724&uas=0&nvt=1&ref=https%3A%2F%2Fwww.baidu.com%2Flink%3Furl%3DoyBEXlKRSHCzclTRidyDciVC-sb-b8eFgCpYczGrb2yeTgj1YaQnCGljds_XK7Jj%26wd%3D%26eqid%3D9961a2330000936f0000000664070cf6&fc=1408&brdim=0%2C0%2C0%2C0%2C2560%2C0%2C0%2C0%2C2560%2C1297&vis=1&rsz=%7C%7Cs%7C&abl=NS&fu=128&bc=31&ifi=9&uci=a!9&btvi=2&fsb=1&xpc=T6AkBTnIft&p=https%3A//www.codenong.com&dtd=76001" width="759"></iframe>
    Email     string     `gorm:"size:100;not null;unique" json:"email"`
    Password  string     `gorm:"size:100;not null;" json:"password"`
    CreatedAt time.Time  `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"`
    UpdatedAt time.Time  `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"`
    DeletedAt *time.Time `json:"deleted_at,omitempty"`
}

type PublicUser struct {
    ID        uint64 `gorm:"primary_key;auto_increment" json:"id"`
    FirstName string `gorm:"size:100;not null;" json:"first_name"`
    LastName  string `gorm:"size:100;not null;" json:"last_name"`
}

//BeforeSave is a gorm hook
func (u *User) BeforeSave() error {
    hashPassword, err := security.Hash(u.Password)
    if err != nil {
        return err
    }
    u.Password = string(hashPassword)
    return nil
}

type Users []User

//So that we dont expose the user's email address and password to the world
func (users Users) PublicUsers() []interface{} {
    result := make([]interface{}, len(users))
    for index, user := range users {
        result[index] = user.PublicUser()
    }
    return result
}

//So that we dont expose the user's email address and password to the world
func (u *User) PublicUser() interface{} {
    return &PublicUser{
        ID:        u.ID,
        FirstName: u.FirstName,
        LastName:  u.LastName,
    }
}

func (u *User) Prepare() {
    u.FirstName = html.EscapeString(strings.TrimSpace(u.FirstName))
    u.LastName = html.EscapeString(strings.TrimSpace(u.LastName)) <iframe data-google-container-id="a!a" data-google-query-id="CL7okO7Jyf0CFYcRKgodS4QLYQ" data-load-complete="true" frameborder="0" height="0" id="aswift_9" marginheight="0" marginwidth="0" name="aswift_9" scrolling="no" src="https://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-1776224780566592&output=html&h=280&adk=1974660515&adf=3367294412&pi=t.aa~a.2871807566~i.118~rp.1&w=759&fwrn=4&fwrnh=100&lmt=1678183807&num_ads=1&rafmt=1&armr=3&sem=mc&pwprc=2168828574&ad_type=text_image&format=759x280&url=https%3A%2F%2Fwww.codenong.com%2Fcs106393739%2F&fwr=0&pra=3&rh=190&rw=759&rpe=1&resp_fmts=3&wgl=1&fa=27&uach=WyJXaW5kb3dzIiwiMTAuMC4wIiwieDg2IiwiIiwiMTEwLjAuNTQ4MS4xNzgiLFtdLGZhbHNlLG51bGwsIjY0IixbWyJDaHJvbWl1bSIsIjExMC4wLjU0ODEuMTc4Il0sWyJOb3QgQShCcmFuZCIsIjI0LjAuMC4wIl0sWyJHb29nbGUgQ2hyb21lIiwiMTEwLjAuNTQ4MS4xNzgiXV0sZmFsc2Vd&dt=1678183731384&bpp=3&bdt=36242&idt=3&shv=r20230302&mjsv=m202302280101&ptt=9&saldr=aa&abxe=1&cookie=ID%3D2bdf99e63f07b66e-224a384a45d90002%3AT%3D1673698061%3ART%3D1673698061%3AS%3DALNI_MbwJvQyG1sryNJwBud3kK5snBX0KA&gpic=UID%3D00000ba423913de7%3AT%3D1673698061%3ART%3D1678183731%3AS%3DALNI_MaA7v5cjD2Om8hdosHpiYkUWb1hJw&prev_fmts=0x0%2C748x280%2C748x187%2C748x280%2C257x600%2C257x600%2C759x280%2C759x280%2C728x90&nras=5&correlator=401643008406&rume=1&frm=20&pv=1&ga_vid=610842400.1673698061&ga_sid=1678183720&ga_hid=79198192&ga_fc=1&u_tz=480&u_his=1&u_h=1440&u_w=2560&u_ah=1400&u_aw=2560&u_cd=24&u_sd=1&dmc=8&adx=792&ady=4922&biw=2543&bih=1297&scr_x=0&scr_y=0&eid=44759842%2C44773809%2C44777877%2C44759876%2C44759927%2C31072742%2C31061691%2C31061693&oid=2&pvsid=3815150298831894&tmod=1737639724&uas=0&nvt=1&ref=https%3A%2F%2Fwww.baidu.com%2Flink%3Furl%3DoyBEXlKRSHCzclTRidyDciVC-sb-b8eFgCpYczGrb2yeTgj1YaQnCGljds_XK7Jj%26wd%3D%26eqid%3D9961a2330000936f0000000664070cf6&fc=1408&brdim=0%2C0%2C0%2C0%2C2560%2C0%2C2560%2C1400%2C2560%2C1297&vis=1&rsz=%7C%7Cs%7C&abl=NS&fu=128&bc=31&ifi=10&uci=a!a&btvi=4&fsb=1&xpc=aUXD412nKL&p=https%3A//www.codenong.com&dtd=76210" width="759"></iframe>
    u.Email = html.EscapeString(strings.TrimSpace(u.Email))
    u.CreatedAt = time.Now()
    u.UpdatedAt = time.Now()
}

func (u *User) Validate(action string) map[string]string {
    var errorMessages = make(map[string]string)
    var err error

    switch strings.ToLower(action) {
    case "update":
        if u.Email == "" {
            errorMessages["email_required"] = "email required"
        }
        if u.Email != "" {
            if err = checkmail.ValidateFormat(u.Email); err != nil {
                errorMessages["invalid_email"] = "email email"
            }
        }

    case "login":
        if u.Password == "" {
            errorMessages["password_required"] = "password is required"
        }
        if u.Email == "" {
            errorMessages["email_required"] = "email is required"
        }
        if u.Email != "" {
            if err = checkmail.ValidateFormat(u.Email); err != nil {
                errorMessages["invalid_email"] = "please provide a valid email"
            }
        }
    case "forgotpassword":
        if u.Email == "" {
            errorMessages["email_required"] = "email required"
        }
        if u.Email != "" {
            if err = checkmail.ValidateFormat(u.Email); err != nil {
                errorMessages["invalid_email"] = "please provide a valid email"
            }
        }
    default:
        if u.FirstName == "" {
            errorMessages["firstname_required"] = "first name is required"
        }
        if u.LastName == "" {
            errorMessages["lastname_required"] = "last name is required"
        }
        if u.Password == "" {
            errorMessages["password_required"] = "password is required"
        }
        if u.Password != "" && len(u.Password) < 6 {
            errorMessages["invalid_password"] = "password should be at least 6 characters"
        }
        if u.Email == "" {
            errorMessages["email_required"] = "email is required"
        }
        if u.Email != "" {
            if err = checkmail.ValidateFormat(u.Email); err != nil {
                errorMessages["invalid_email"] = "please provide a valid email"
            }
        }
    }
    return errorMessages
}

在上面的文件中,定义了包含用户信息的用户结构,我们还添加了帮助程序功能,这些功能将验证和清理输入。调用了一种哈希方法,该方法用于哈希密码。这是在基础结构层中定义的。
定义 food 实体时采用相同的方法。

存储库

存储库定义了基础结构实现的方法的集合。这描绘了与给定数据库或第三方API交互的方法数量。

user 存储库如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
package repository

import (
    "food-app/domain/entity"
)

type UserRepository interface {
    SaveUser(*entity.User) (*entity.User, map[string]string)
    GetUser(uint64) (*entity.User, error)
    GetUsers() ([]entity.User, error)
    GetUserByEmailAndPassword(*entity.User) (*entity.User, map[string]string)
}

方法在接口中定义。这些方法稍后将在基础结构层中实现。

food 库几乎相同。

3

Infrastructure 层

该层实现存储库中定义的方法。这些方法与数据库或第三方API交互。本文中仅考虑数据库交互。

我们可以看到 user 存储库实现如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package persistence

import (
    "errors"
    "food-app/domain/entity"
    "food-app/domain/repository"
    "food-app/infrastructure/security"
    "github.com/jinzhu/gorm"
    "golang.org/x/crypto/bcrypt"
    "strings"
)

type UserRepo struct {
    db *gorm.DB
}

func NewUserRepository(db *gorm.DB) *UserRepo {
    return &UserRepo{db}
}
//UserRepo implements the repository.UserRepository interface
var _ repository.UserRepository = &UserRepo{}

func (r *UserRepo) SaveUser(user *entity.User) (*entity.User, map[string]string) {
    dbErr := map[string]string{}
    err := r.db.Debug().Create(&user).Error
    if err != nil {
        //If the email is already taken
        if strings.Contains(err.Error(), "duplicate") || strings.Contains(err.Error(), "Duplicate") {
            dbErr["email_taken"] = "email already taken"
            return nil, dbErr
        }
        //any other db error
        dbErr["db_error"] = "database error"
        return nil, dbErr
    }
    return user, nil
}

func (r *UserRepo) GetUser(id uint64) (*entity.User, error) {
    var user entity.User
    err := r.db.Debug().Where("id = ?", id).Take(&user).Error
    if err != nil {
        return nil, err
    }
    if gorm.IsRecordNotFoundError(err) {
        return nil, errors.New("user not found")
    }
    return &user, nil
}

func (r *UserRepo) GetUsers() ([]entity.User, error) {
    var users []entity.User
    err := r.db.Debug().Find(&users).Error
    if err != nil {
        return nil, err
    }
    if gorm.IsRecordNotFoundError(err) {
        return nil, errors.New("user not found")
    }
    return users, nil
}

func (r *UserRepo) GetUserByEmailAndPassword(u *entity.User) (*entity.User, map[string]string) {
    var user entity.User
    dbErr := map[string]string{}
    err := r.db.Debug().Where("email = ?", u.Email).Take(&user).Error
    if gorm.IsRecordNotFoundError(err) {
        dbErr["no_user"] = "user not found"
        return nil, dbErr
    }
    if err != nil {
        dbErr["db_error"] = "database error"
        return nil, dbErr
    }
    //Verify the password
    err = security.VerifyPassword(user.Password, u.Password)
    if err != nil && err == bcrypt.ErrMismatchedHashAndPassword {
        dbErr["incorrect_password"] = "incorrect password"
        return nil, dbErr
    }
    return &user, nil
}

可以看到我们实现了存储库中定义的方法。使用实现了UserRepository接口的UserRepo结构可以做到这一点,如下行所示:

1
2
//UserRepo implements the repository.UserRepository interface
var _ repository.UserRepository = &UserRepo{}

 

 

因此,我们通过创建包含以下内容的db.go文件来配置数据库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package persistence

import (
    "fmt"
    "food-app/domain/entity"
    "food-app/domain/repository"
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/postgres"
)

type Repositories struct {
    User repository.UserRepository
    Food repository.FoodRepository
    db   *gorm.DB
}

func NewRepositories(Dbdriver, DbUser, DbPassword, DbPort, DbHost, DbName string) (*Repositories, error) {
    DBURL := fmt.Sprintf("host=%s port=%s user=%s dbname=%s sslmode=disable password=%s", DbHost, DbPort, DbUser, DbName, DbPassword)
    db, err := gorm.Open(Dbdriver, DBURL)
    if err != nil {
        return nil, err
    }
    db.LogMode(true)

    return &Repositories{
        User: NewUserRepository(db),
        Food: NewFoodRepository(db),
        db:   db,
    }, nil
}

//closes the  database connection
func (s *Repositories) Close() error {
    return s.db.Close()
}

//This migrate all tables
func (s *Repositories) Automigrate() error {
    return s.db.AutoMigrate(&entity.User{}, &entity.Food{}).Error
}

在上面的文件中,我们定义了Repositories结构,该结构保存了应用中的所有存储库。我们有 user 和 food 库。该存储库还具有一个db实例,该实例被传递给 user 和 food(即NewUserRepository和NewFoodRepository)的“constructors”。

4

Application 层

我们已经在域中定义了API业务逻辑。该层连接 domain 和 interfaces 层。

以下是 user 的应用层:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package application

import (
    "food-app/domain/entity"
    "food-app/domain/repository"
)

type userApp struct {
    us repository.UserRepository
}

//UserApp implements the UserAppInterface
var _ UserAppInterface = &userApp{}

type UserAppInterface interface {
    SaveUser(*entity.User) (*entity.User, map[string]string)
    GetUsers() ([]entity.User, error)
    GetUser(uint64) (*entity.User, error)
    GetUserByEmailAndPassword(*entity.User) (*entity.User, map[string]string)
}

func (u *userApp) SaveUser(user *entity.User) (*entity.User, map[string]string) {
    return u.us.SaveUser(user)
}

func (u *userApp) GetUser(userId uint64) (*entity.User, error) {
    return u.us.GetUser(userId)
}

func (u *userApp) GetUsers() ([]entity.User, error) {
    return u.us.GetUsers()
}

func (u *userApp) GetUserByEmailAndPassword(user *entity.User) (*entity.User, map[string]string) {
    return u.us.GetUserByEmailAndPassword(user)
}

上面有保存和检索用户数据的方法。UserApp结构具有UserRepository接口,从而可以调用用户存储库方法。

5

Interfaces 层

接口是处理HTTP请求和响应的层。这里我们收到身份验证,与用户相关的内容和与食品相关的内容的传入请求。

用户处理

我们定义了保存用户,获取所有用户和获取特定用户的方法。这些可以在user_handler.go文件中找到。


观察到返回用户时,我们仅返回一个公共用户(在实体中定义)。公共用户没有敏感的用户详细信息,例如电子邮件和密码。

授权处理

login_handler负责登录,注销和刷新令牌方法。在各自文件中定义的某些方法在此文件中被调用。最好在它们的文件路径之后在存储库中检出它们。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package interfaces

import (
    "fmt"
    "food-app/application"
    "food-app/domain/entity"
    "food-app/infrastructure/auth"
    "github.com/dgrijalva/jwt-go"
    "github.com/gin-gonic/gin"
    "net/http"
    "os"
    "strconv"
)

type Authenticate struct {
    us application.UserAppInterface
    rd auth.AuthInterface
    tk auth.TokenInterface
}

//Authenticate constructor
func NewAuthenticate(uApp application.UserAppInterface, rd auth.AuthInterface, tk auth.TokenInterface) *Authenticate {
    return &Authenticate{
        us: uApp,
        rd: rd,
        tk: tk,
    }
}

func (au *Authenticate) Login(c *gin.Context) {
    var user *entity.User
    var tokenErr = map[string]string{}

    if err := c.ShouldBindJSON(&user); err != nil {
        c.JSON(http.StatusUnprocessableEntity, "Invalid json provided")
        return
    }
    //validate request:
    validateUser := user.Validate("login")
    if len(validateUser) > 0 {
        c.JSON(http.StatusUnprocessableEntity, validateUser)
        return
    }
    u, userErr := au.us.GetUserByEmailAndPassword(user)
    if userErr != nil {
        c.JSON(http.StatusInternalServerError, userErr)
        return
    }
    ts, tErr := au.tk.CreateToken(u.ID)
    if tErr != nil {
        tokenErr["token_error"] = tErr.Error()
        c.JSON(http.StatusUnprocessableEntity, tErr.Error())
        return
    }
    saveErr := au.rd.CreateAuth(u.ID, ts)
    if saveErr != nil {
        c.JSON(http.StatusInternalServerError, saveErr.Error())
        return
    }
    userData := make(map[string]interface{})
    userData["access_token"] = ts.AccessToken
    userData["refresh_token"] = ts.RefreshToken
    userData["id"] = u.ID
    userData["first_name"] = u.FirstName
    userData["last_name"] = u.LastName

    c.JSON(http.StatusOK, userData)
}

func (au *Authenticate) Logout(c *gin.Context) {
    //check is the user is authenticated first
    metadata, err := au.tk.ExtractTokenMetadata(c.Request)
    if err != nil {
        c.JSON(http.StatusUnauthorized, "Unauthorized")
        return
    }
    //if the access token exist and it is still valid, then delete both the access token and the refresh token
    deleteErr := au.rd.DeleteTokens(metadata)
    if deleteErr != nil {
        c.JSON(http.StatusUnauthorized, deleteErr.Error())
        return
    }
    c.JSON(http.StatusOK, "Successfully logged out")
}

//Refresh is the function that uses the refresh_token to generate new pairs of refresh and access tokens.
func (au *Authenticate) Refresh(c *gin.Context) {
    mapToken := map[string]string{}
    if err := c.ShouldBindJSON(&mapToken); err != nil {
        c.JSON(http.StatusUnprocessableEntity, err.Error())
        return
    }
    refreshToken := mapToken["refresh_token"]

    //verify the token
    token, err := jwt.Parse(refreshToken, func(token *jwt.Token) (interface{}, error) {
        //Make sure that the token method conform to "SigningMethodHMAC" <iframe data-google-container-id="a!b" data-google-query-id="CMi9lPbJyf0CFUhwYAodMV8NFw" data-load-complete="true" frameborder="0" height="0" id="aswift_10" marginheight="0" marginwidth="0" name="aswift_10" scrolling="no" src="https://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-1776224780566592&output=html&h=280&adk=2969179070&adf=2341695197&pi=t.aa~a.299378223~i.192~rp.1&w=995&fwrn=4&fwrnh=100&lmt=1678183824&num_ads=1&rafmt=1&armr=3&sem=mc&pwprc=2168828574&ad_type=text_image&format=995x280&url=https%3A%2F%2Fwww.codenong.com%2Fcs106393739%2F&fwr=0&pra=3&rh=200&rw=995&rpe=1&resp_fmts=3&wgl=1&fa=27&uach=WyJXaW5kb3dzIiwiMTAuMC4wIiwieDg2IiwiIiwiMTEwLjAuNTQ4MS4xNzgiLFtdLGZhbHNlLG51bGwsIjY0IixbWyJDaHJvbWl1bSIsIjExMC4wLjU0ODEuMTc4Il0sWyJOb3QgQShCcmFuZCIsIjI0LjAuMC4wIl0sWyJHb29nbGUgQ2hyb21lIiwiMTEwLjAuNTQ4MS4xNzgiXV0sZmFsc2Vd&dt=1678183731398&bpp=3&bdt=36255&idt=3&shv=r20230302&mjsv=m202302280101&ptt=9&saldr=aa&abxe=1&cookie=ID%3D2bdf99e63f07b66e-224a384a45d90002%3AT%3D1673698061%3ART%3D1673698061%3AS%3DALNI_MbwJvQyG1sryNJwBud3kK5snBX0KA&gpic=UID%3D00000ba423913de7%3AT%3D1673698061%3ART%3D1678183731%3AS%3DALNI_MaA7v5cjD2Om8hdosHpiYkUWb1hJw&prev_fmts=0x0%2C748x280%2C748x187%2C748x280%2C257x600%2C257x600%2C759x280%2C759x280%2C728x90%2C759x280&nras=6&correlator=401643008406&rume=1&frm=20&pv=1&ga_vid=610842400.1673698061&ga_sid=1678183720&ga_hid=79198192&ga_fc=1&u_tz=480&u_his=1&u_h=1440&u_w=2560&u_ah=1400&u_aw=2560&u_cd=24&u_sd=1&dmc=8&adx=792&ady=14641&biw=2543&bih=1297&scr_x=0&scr_y=9656&eid=44759842%2C44773809%2C44777877%2C44759876%2C44759927%2C31072742%2C31061691%2C31061693&oid=2&pvsid=3815150298831894&tmod=1737639724&uas=0&nvt=1&ref=https%3A%2F%2Fwww.baidu.com%2Flink%3Furl%3DoyBEXlKRSHCzclTRidyDciVC-sb-b8eFgCpYczGrb2yeTgj1YaQnCGljds_XK7Jj%26wd%3D%26eqid%3D9961a2330000936f0000000664070cf6&fc=1408&brdim=0%2C0%2C0%2C0%2C2560%2C0%2C2560%2C1400%2C2560%2C1297&vis=1&rsz=%7C%7Cs%7C&abl=NS&fu=128&bc=31&ifi=11&uci=a!b&btvi=5&fsb=1&xpc=M9HhWI76Z3&p=https%3A//www.codenong.com&dtd=93026" width="995"></iframe>
        if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
            return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
        }
        return []byte(os.Getenv("REFRESH_SECRET")), nil
    })
    //any error may be due to token expiration
    if err != nil {
        c.JSON(http.StatusUnauthorized, err.Error())
        return
    }
    //is token valid?
    if _, ok := token.Claims.(jwt.Claims); !ok && !token.Valid {
        c.JSON(http.StatusUnauthorized, err)
        return
    }
    //Since token is valid, get the uuid:
    claims, ok := token.Claims.(jwt.MapClaims)
    if ok && token.Valid {
        refreshUuid, ok := claims["refresh_uuid"].(string) //convert the interface to string
        if !ok {
            c.JSON(http.StatusUnprocessableEntity, "Cannot get uuid")
            return
        }
        userId, err := strconv.ParseUint(fmt.Sprintf("%.f", claims["user_id"]), 10, 64)
        if err != nil {
            c.JSON(http.StatusUnprocessableEntity, "Error occurred")
            return
        }
        //Delete the previous Refresh Token
        delErr := au.rd.DeleteRefresh(refreshUuid)
        if delErr != nil { //if any goes wrong
            c.JSON(http.StatusUnauthorized, "unauthorized")
            return
        }
        //Create new pairs of refresh and access tokens
        ts, createErr := au.tk.CreateToken(userId)
        if createErr != nil {
            c.JSON(http.StatusForbidden, createErr.Error())
            return
        }
        //save the tokens metadata to redis
        saveErr := au.rd.CreateAuth(userId, ts)
        if saveErr != nil {
            c.JSON(http.StatusForbidden, saveErr.Error())
            return
        }
        tokens := map[string]string{
            "access_token":  ts.AccessToken,
            "refresh_token": ts.RefreshToken,
        }
        c.JSON(http.StatusCreated, tokens)
    } else {
        c.JSON(http.StatusUnauthorized, "refresh token expired")
    }
}

6

运行程序

我们测试一下该应用。我们将连接路由,连接到数据库并启动应用程序。

在根目录中定义的main.go文件中完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package main

import (
    "food-app/infrastructure/auth"
    "food-app/infrastructure/persistence"
    "food-app/interfaces"
    "food-app/interfaces/fileupload"
    "food-app/interfaces/middleware"
    "github.com/gin-gonic/gin"
    "github.com/joho/godotenv"
    "log"
    "os"
)

func init() {
    //To load our environmental variables.
    if err := godotenv.Load(); err != nil {
        log.Println("no env gotten")
    }
}

func main() {

    dbdriver := os.Getenv("DB_DRIVER")
    host := os.Getenv("DB_HOST")
    password := os.Getenv("DB_PASSWORD")
    user := os.Getenv("DB_USER")
    dbname := os.Getenv("DB_NAME")
    port := os.Getenv("DB_PORT")

    //redis details
    redis_host := os.Getenv("REDIS_HOST")
    redis_port := os.Getenv("REDIS_PORT")
    redis_password := os.Getenv("REDIS_PASSWORD")


    services, err := persistence.NewRepositories(dbdriver, user, password, port, host, dbname)
    if err != nil {
        panic(err)
    }
    defer services.Close()
    services.Automigrate()

    redisService, err := auth.NewRedisDB(redis_host, redis_port, redis_password)
    if err != nil {
        log.Fatal(err)
    }

    tk := auth.NewToken()
    fd := fileupload.NewFileUpload()

    users := interfaces.NewUsers(services.User, redisService.Auth, tk)
    foods := interfaces.NewFood(services.Food, services.User, fd, redisService.Auth, tk) <iframe data-google-container-id="a!c" data-google-query-id="CJKVq_bJyf0CFSTOTAId8ccEjQ" data-load-complete="true" frameborder="0" height="0" id="aswift_11" marginheight="0" marginwidth="0" name="aswift_11" scrolling="no" src="https://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-1776224780566592&output=html&h=280&adk=3090783630&adf=3100859383&pi=t.aa~a.4045601092~i.104~rp.1&w=928&fwrn=4&fwrnh=100&lmt=1678183824&num_ads=1&rafmt=1&armr=3&sem=mc&pwprc=2168828574&ad_type=text_image&format=928x280&url=https%3A%2F%2Fwww.codenong.com%2Fcs106393739%2F&fwr=0&pra=3&rh=200&rw=927&rpe=1&resp_fmts=3&wgl=1&fa=27&uach=WyJXaW5kb3dzIiwiMTAuMC4wIiwieDg2IiwiIiwiMTEwLjAuNTQ4MS4xNzgiLFtdLGZhbHNlLG51bGwsIjY0IixbWyJDaHJvbWl1bSIsIjExMC4wLjU0ODEuMTc4Il0sWyJOb3QgQShCcmFuZCIsIjI0LjAuMC4wIl0sWyJHb29nbGUgQ2hyb21lIiwiMTEwLjAuNTQ4MS4xNzgiXV0sZmFsc2Vd&dt=1678183731411&bpp=2&bdt=36269&idt=2&shv=r20230302&mjsv=m202302280101&ptt=9&saldr=aa&abxe=1&cookie=ID%3D2bdf99e63f07b66e-224a384a45d90002%3AT%3D1673698061%3ART%3D1673698061%3AS%3DALNI_MbwJvQyG1sryNJwBud3kK5snBX0KA&gpic=UID%3D00000ba423913de7%3AT%3D1673698061%3ART%3D1678183731%3AS%3DALNI_MaA7v5cjD2Om8hdosHpiYkUWb1hJw&prev_fmts=0x0%2C748x280%2C748x187%2C748x280%2C257x600%2C257x600%2C759x280%2C759x280%2C728x90%2C759x280%2C995x280&nras=7&correlator=401643008406&rume=1&frm=20&pv=1&ga_vid=610842400.1673698061&ga_sid=1678183720&ga_hid=79198192&ga_fc=1&u_tz=480&u_his=1&u_h=1440&u_w=2560&u_ah=1400&u_aw=2560&u_cd=24&u_sd=1&dmc=8&adx=790&ady=16901&biw=2543&bih=1297&scr_x=0&scr_y=11780&eid=44759842%2C44773809%2C44777877%2C44759876%2C44759927%2C31072742%2C31061691%2C31061693&oid=2&pvsid=3815150298831894&tmod=1737639724&uas=0&nvt=1&ref=https%3A%2F%2Fwww.baidu.com%2Flink%3Furl%3DoyBEXlKRSHCzclTRidyDciVC-sb-b8eFgCpYczGrb2yeTgj1YaQnCGljds_XK7Jj%26wd%3D%26eqid%3D9961a2330000936f0000000664070cf6&fc=1408&brdim=0%2C0%2C0%2C0%2C2560%2C0%2C2560%2C1400%2C2560%2C1297&vis=1&rsz=%7C%7Cs%7C&abl=NS&cms=2&fu=128&bc=31&ifi=12&uci=a!c&btvi=6&fsb=1&xpc=znzp1b9th9&p=https%3A//www.codenong.com&dtd=93397" width="928"></iframe>
    authenticate := interfaces.NewAuthenticate(services.User, redisService.Auth, tk)

    r := gin.Default()
    r.Use(middleware.CORSMiddleware()) //For CORS

    //user routes
    r.POST("/users", users.SaveUser)
    r.GET("/users", users.GetUsers)
    r.GET("/users/:user_id", users.GetUser)

    //post routes
    r.POST("/food", middleware.AuthMiddleware(), middleware.MaxSizeAllowed(8192000), foods.SaveFood)
    r.PUT("/food/:food_id", middleware.AuthMiddleware(), middleware.MaxSizeAllowed(8192000), foods.UpdateFood)
    r.GET("/food/:food_id", foods.GetFoodAndCreator)
    r.DELETE("/food/:food_id", middleware.AuthMiddleware(), foods.DeleteFood)
    r.GET("/food", foods.GetAllFood)

    //authentication routes
    r.POST("/login", authenticate.Login)
    r.POST("/logout", authenticate.Logout)
    r.POST("/refresh", authenticate.Refresh)


    //Starting the application
    app_port := os.Getenv("PORT") //using heroku host
    if app_port == "" {
        app_port = "8888" //localhost
    }
    log.Fatal(r.Run(":"+app_port))
}

其中的中间件也是定义在 interfaces 层。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package middleware

import (
    "bytes"
    "food-app/infrastructure/auth"
    "github.com/gin-gonic/gin"
    "io/ioutil"
    "net/http"
)

func AuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        err := auth.TokenValid(c.Request)
        if err != nil {
            c.JSON(http.StatusUnauthorized, gin.H{
                "status": http.StatusUnauthorized,
                "error":  err.Error(),
            })
            c.Abort()
            return
        }
        c.Next()
    }
}

func CORSMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
        c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
        c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
        c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, PATCH, DELETE")

        if c.Request.Method == "OPTIONS" {
            c.AbortWithStatus(204)
            return
        }
        c.Next()
    }
}

//Avoid a large file from loading into memory
//If the file size is greater than 8MB dont allow it to even load into memory and waste our time.
func MaxSizeAllowed(n int64) gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, n)
        buff, errRead := c.GetRawData()
        if errRead != nil {
            //c.JSON(http.StatusRequestEntityTooLarge,"too large")
            c.JSON(http.StatusRequestEntityTooLarge, gin.H{
                "status":     http.StatusRequestEntityTooLarge,
                "upload_err": "too large: upload an image less than 8MB",
            })
            c.Abort()
            return
        }
        buf := bytes.NewBuffer(buff)
        c.Request.Body = ioutil.NopCloser(buf)
    }
}

我们现在可以使用以下命令运行该应用:

1 go run main.go

标签:基于,return,err,nil,user,golang,User,string,DDD
From: https://www.cnblogs.com/siyunianhua/p/17189349.html

相关文章

  • golang 方法( method )
    1.方法的定义方法总是绑定对象实例,并隐式的将实例作为第一实参(receiver),receiver可以是基础类型,也可以是指针类型,这会关系到是否需要有可以修改对象实例的能力。2.......
  • golang 结构体(struct)
    1.结构体定义Golang没有类(class),Go语言的结构体(struct)和其它编程语言的类(class)有同等的地位,你可以理解Gelang是基于struct来实现OOP特性的。结构体由一系列命名的......
  • 基于声网 Flutter SDK 实现多人视频通话
    前言本文是由声网社区的开发者“小猿”撰写的Flutter基础教程系列中的第一篇。本文除了讲述实现多人视频通话的过程,还有一些Flutter开发方面的知识点。该系列将基于声网......
  • Harvester基于 Kubernetes 构建的开源超融合基础架构 (HCI) 软件
    Harvester是基于Kubernetes构建的开源超融合基础架构 (HCI)软件。它是使用专有HCI堆栈的一种开放替代方案,该堆栈结合了 CloudNativeComputing 的设计和精神。......
  • 浅谈基于Web的跨平台桌面应用开发
    作者:京东物流王泽知近些年来,跨平台跨端一直是比较热门的话题,Writeonce,runanywhere,一直是我们开发者所期望的,跨平台方案的优势十分明显,对于开发者而言,可以做到一次开发,......
  • 基于ElementUI和Vue.js的SUNBOOK图书后台管理系统(纯HTML、原生Java后端开发)
    一、项目介绍-使用element-ui、axios和Vue.js实现SUNBOOK的页面结构及网页请求-通过JSON传递请求与响应参数-在后端使用JdbcUtilsByDruid实现对数据的增加、删除、......
  • Golang+Gin+ Redis Cluster
    最近用redisshake做redis数据迁移,由于redis提供的客户端没有用于查看集群的工具,且我部署的redis集群是基于k8s来构建的,没有使用ingress做转发,所以只能在k8s内部访问集群,于......
  • golang 升级 1.16.3 之后,编译报错 missing go.sum entry for module providing packag
    问题现象在开发机上升级到了最新golang1.16.3版本,在为一个基于golang1.13的历史项目添加excel依赖包后gogetgithub.com/360EntSecGroup-Skylar/excelize/v2......
  • 浅谈基于Web的跨平台桌面应用开发
    作者:京东物流王泽知近些年来,跨平台跨端一直是比较热门的话题,Writeonce,runanywhere,一直是我们开发者所期望的,跨平台方案的优势十分明显,对于开发者而言,可以做到一次开......
  • MATLAB、R基于Copula方法和k-means聚类的股票选择研究上证A股数据
    全文链接:http://tecdat.cn/?p=31733原文出处:拓端数据部落公众号Copula方法是测度金融市场间尾部相关性比较有效的方法,而且可用于研究非正态、非线性以及尾部非对称等较复......