├── .env ├── .gitignore ├── Dockerfile ├── Makefile ├── README.md ├── application └── uploadimg_app.go ├── cmd ├── imgupload │ └── main.go └── imgupload_server │ └── main.go ├── docker-compose.yml ├── docs ├── 20201017_171542.jpg └── go_echo_doc.md ├── domain ├── entity │ └── uploadimg.go └── repository │ └── uploadimg_repo.go ├── go.mod ├── go.sum ├── infrastructure └── persistence │ ├── db.go │ └── uploadimg_persis.go ├── interfaces └── api │ ├── handler │ └── uploadimg_handler.go │ └── middleware │ └── middleware.go ├── public └── index.html └── tests ├── helloworld.http └── http-client.env.json /.env: -------------------------------------------------------------------------------- 1 | # APP 2 | APP_ENV=local 3 | LISTEN_PORT=:1818 4 | 5 | # Image Storage 6 | IMAGE_STORAGE=/tmp/uploadImg 7 | IMAGE_DOMAIN=http://localhost 8 | 9 | # Mysql 10 | DB_DRIVER=mysql 11 | DB_HOST=127.0.0.1 12 | DB_USER=root 13 | DB_PASSWORD=Secret123. 14 | DB_NAME=img-upload 15 | DB_PORT=3306 16 | 17 | #Redis 18 | REDIS_HOST=127.0.0.1 19 | REDIS_PORT=6379 20 | REDIS_PASSWORD=clark -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | /goimgrz 4 | /bin/* 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.11 as builder 2 | #WORKDIR $GOPATH/src/github.com/johan-lejdung/go-microservice-api-guide/rest-api 3 | #COPY ./ . 4 | #RUN GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -v 5 | #RUN cp rest-api / 6 | # 7 | #FROM alpine:latest 8 | #COPY --from=builder /rest-api / 9 | #CMD ["/rest-api"] -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SUBPACKAGES := $(shell go list ./...) 2 | 3 | .DEFAULT_GOAL := help 4 | 5 | .PHONY: generate 6 | generate: ## Invoke go generate 7 | go generate ./... 8 | 9 | .PHONY: test 10 | test: ## Run go test 11 | go test -v$(SUBPACKAGES) 12 | 13 | .PHONY: help 14 | help: ## Help 15 | @grep -E '^[0-9a-zA-Z_/()$$-]+:.*?## .*$$' $(lastword $(MAKEFILE_LIST)) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 最新布局参考 2 | 3 | - go-ddd-layout: https://github.com/lupguo/go-ddd-layout.git 4 | - Blog文章: https://tkstorm.com/ln/ddd-summary-2024 5 | 6 | ## ~~Go DDD微服务设计分层最佳实践~~ 7 | 微服务基于DDD领域驱动设计 8 | 9 | ### 运行 10 | 需要先创建DB,具体参数参考`.env`配置 11 | 12 | #### 编译运行HTTP服务 13 | ``` 14 | // 拉代码 15 | git clone github.com/lupguo/go-ddd-sample 16 | // 编译 17 | cd go-ddd-sample 18 | go build ./cmd/imgupload_server 19 | // 运行 20 | ./impgupload_server 21 | ``` 22 | 23 | ### DDD分层目录结构 24 | 25 | ``` 26 | $ tree -d -NL 2 27 | . 28 | ├── application // [必须]DDD - 应用层 29 | ├── cmd // [必须]参考project-layout,存放CMD 30 | │ ├── imgupload // 命令行上传图片 31 | │ └── imgupload_server // 命令行启动Httpd服务 32 | ├── deployments // 参考project-layout,服务部署相关 33 | ├── docs // 参考project-layout,文档相关 34 | ├── domain // [必须]DDD - 领域层 35 | │ ├── entity // - 领域实体 36 | │ ├── repository // - 领域仓储接口 37 | │ ├── service // - 领域服务,多个实体的能力聚合 38 | │ └── valobj // - 领域值对象 39 | ├── infrastructure // [必须]DDD - 基础层 40 | │ └── persistence // - 数据库持久层 41 | ├── interfaces // [必须]DDD - 接口层 42 | │ └── api // - RESTful API接口对外暴露 43 | ├── pkg // [可选]参考project-layout,项目包,还有internel等目录结构,依据服务实际情况考虑 44 | └── tests // [可选]参考project-layout,测试相关 45 | └── mock 46 | ``` 47 | ### 其他目录结构 48 | - cmd: 命令文件生成 49 | - deployments: 服务部署相关 50 | - tests: 服务测试相关 51 | - docs: 服务文档相关 52 | - .env: 服务环境配置(可以基于config配置) 53 | 54 | ### TODO 55 | 1. 其余几个接口补充 56 | 2. 前端展示 57 | 3. 剩余模块服务补充 58 | 4. Dockerfile和Makefile补充 59 | 5. 更合适的文件或目录命名 60 | 61 | ### 参考 62 | 1. Duck-DDD: https://medium.com/learnfazz/domain-driven-design-in-go-253155543bb1 63 | 2. https://dev.to/stevensunflash/using-domain-driven-design-ddd-in-golang-3ee5 64 | 3. https://github.com/takashabe/go-ddd-sample 65 | 4. https://github.com/victorsteven/food-app-server 66 | -------------------------------------------------------------------------------- /application/uploadimg_app.go: -------------------------------------------------------------------------------- 1 | package application 2 | 3 | import ( 4 | "github.com/lupguo/go-ddd-sample/domain/entity" 5 | "github.com/lupguo/go-ddd-sample/domain/repository" 6 | "os" 7 | ) 8 | 9 | // UploadImgAppIer 应用接口 10 | type UploadImgAppIer interface { 11 | Save(*entity.UploadImg) (*entity.UploadImg, error) 12 | Get(uint64) (*entity.UploadImg, error) 13 | GetAll() ([]entity.UploadImg, error) 14 | Delete(uint64) error 15 | } 16 | 17 | // UploadImgApp 上传应用结构体,也是依赖领域仓储接口 18 | type UploadImgApp struct { 19 | db repository.UploadImgRepo 20 | } 21 | 22 | // NewUploadImgApp 初始化上传图片应用 23 | func NewUploadImgApp(db repository.UploadImgRepo) *UploadImgApp { 24 | return &UploadImgApp{db: db} 25 | } 26 | 27 | func (app *UploadImgApp) Save(img *entity.UploadImg) (*entity.UploadImg, error) { 28 | img, err := app.db.Save(img) 29 | if err != nil { 30 | return nil, err 31 | } 32 | img.Url = rawUrl(img.Path) 33 | return img, nil 34 | } 35 | 36 | func (app *UploadImgApp) Get(id uint64) (*entity.UploadImg, error) { 37 | img, err := app.db.Get(id) 38 | if err != nil { 39 | return nil, err 40 | } 41 | img.Url = rawUrl(img.Path) 42 | return img, nil 43 | } 44 | 45 | func (app *UploadImgApp) GetAll() ([]entity.UploadImg, error) { 46 | imgs, err := app.db.GetAll() 47 | if err != nil { 48 | return nil, err 49 | } 50 | for i, img := range imgs { 51 | imgs[i].Url = rawUrl(img.Path) 52 | } 53 | return imgs, nil 54 | } 55 | 56 | func (app *UploadImgApp) Delete(id uint64) error { 57 | return app.db.Delete(id) 58 | } 59 | 60 | func rawUrl(path string) string { 61 | return os.Getenv("IMAGE_DOMAIN") + os.Getenv("LISTEN_PORT") + path 62 | } -------------------------------------------------------------------------------- /cmd/imgupload/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | // TODO imgUpload command 5 | } -------------------------------------------------------------------------------- /cmd/imgupload_server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/joho/godotenv" 5 | "github.com/labstack/echo" 6 | "github.com/labstack/echo/middleware" 7 | "github.com/lupguo/go-ddd-sample/application" 8 | "github.com/lupguo/go-ddd-sample/infrastructure/persistence" 9 | "github.com/lupguo/go-ddd-sample/interfaces/api/handler" 10 | "log" 11 | "os" 12 | ) 13 | 14 | func init() { 15 | // To load our environmental variables. 16 | if err := godotenv.Load(); err != nil { 17 | log.Println("no env gotten") 18 | } 19 | } 20 | 21 | func main() { 22 | // db detail 23 | dbDriver := os.Getenv("DB_DRIVER") 24 | host := os.Getenv("DB_HOST") 25 | password := os.Getenv("DB_PASSWORD") 26 | user := os.Getenv("DB_USER") 27 | dbname := os.Getenv("DB_NAME") 28 | port := os.Getenv("DB_PORT") 29 | 30 | // 初始化基础层实例 - DB实例 31 | persisDB, err := persistence.NewRepositories(dbDriver, user, password, port, host, dbname) 32 | if err != nil { 33 | log.Fatal(err) 34 | } 35 | defer persisDB.Close() 36 | // db做Migrate 37 | if err := persisDB.AutoMigrate(); err != nil { 38 | log.Fatal(err) 39 | } 40 | 41 | // 初始化应用层实例 - 上传图片应用 42 | uploadImgApp := application.NewUploadImgApp(persisDB.UploadImg) 43 | // 初始化接口层实例 - HTTP处理 44 | uploadImgHandler := handler.NewUploadImgHandler(uploadImgApp) 45 | 46 | e := echo.New() 47 | e.Use(middleware.Logger()) 48 | e.Use(middleware.Recover()) 49 | 50 | // 静态主页 51 | e.Static("/", "public") 52 | 53 | // 图片上传 54 | e.POST("/upload", uploadImgHandler.Save) 55 | e.GET("/delete/:id", uploadImgHandler.Delete) 56 | e.GET("/img/:id", uploadImgHandler.Get) 57 | e.GET("/img-list", uploadImgHandler.GetAll) 58 | 59 | // Start server 60 | e.Logger.Fatal(e.Start(os.Getenv("LISTEN_PORT"))) 61 | } 62 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lupguo/go-ddd-sample/e3754a4b7d11dab3bc87f0b4d909dd27d5096f2f/docker-compose.yml -------------------------------------------------------------------------------- /docs/20201017_171542.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lupguo/go-ddd-sample/e3754a4b7d11dab3bc87f0b4d909dd27d5096f2f/docs/20201017_171542.jpg -------------------------------------------------------------------------------- /docs/go_echo_doc.md: -------------------------------------------------------------------------------- 1 | ``` 2 | 3 | ``` -------------------------------------------------------------------------------- /domain/entity/uploadimg.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import ( 4 | "os" 5 | "time" 6 | ) 7 | 8 | // UploadImg 上传图片实体 9 | type UploadImg struct { 10 | ID uint64 `gorm:"primary_key;auto_increment" json:"id"` 11 | Name string `gorm:"size:100;not null;" json:"name"` 12 | Path string `gorm:"size:100;" json:"path"` 13 | Url string `gorm:"-" json:"url"` 14 | Content os.File `gorm:"-" json:"-"` 15 | CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"` 16 | UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"` 17 | DeletedAt *time.Time `json:"-"` 18 | } 19 | -------------------------------------------------------------------------------- /domain/repository/uploadimg_repo.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import "github.com/lupguo/go-ddd-sample/domain/entity" 4 | 5 | // UploadImgRepo 图片上传相关仓储接口,只要实现了该接口,则可以操作Domain领域实体 6 | type UploadImgRepo interface { 7 | Save(*entity.UploadImg) (*entity.UploadImg, error) 8 | Get(uint64) (*entity.UploadImg, error) 9 | GetAll() ([]entity.UploadImg, error) 10 | Delete(uint64) error 11 | } 12 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/lupguo/go-ddd-sample 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/badoux/checkmail v1.2.1 // indirect 7 | github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect 8 | github.com/go-redis/redis/v7 v7.4.0 // indirect 9 | github.com/go-sql-driver/mysql v1.5.0 10 | github.com/jinzhu/gorm v1.9.16 11 | github.com/joho/godotenv v1.3.0 12 | github.com/labstack/echo v3.3.10+incompatible 13 | github.com/labstack/gommon v0.3.0 // indirect 14 | github.com/mattn/go-colorable v0.1.7 // indirect 15 | github.com/myesui/uuid v1.0.0 // indirect 16 | github.com/stretchr/testify v1.4.0 // indirect 17 | github.com/twinj/uuid v1.0.0 // indirect 18 | github.com/valyala/fasttemplate v1.2.1 // indirect 19 | golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect 20 | golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect 21 | golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6 // indirect 22 | golang.org/x/text v0.3.3 // indirect 23 | ) 24 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= 2 | github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= 3 | github.com/badoux/checkmail v1.2.1 h1:TzwYx5pnsV6anJweMx2auXdekBwGr/yt1GgalIx9nBQ= 4 | github.com/badoux/checkmail v1.2.1/go.mod h1:XroCOBU5zzZJcLvgwU15I+2xXyCdTWXyR9MGfRhBYy0= 5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= 7 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 8 | github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= 9 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 10 | github.com/go-redis/redis/v7 v7.4.0 h1:7obg6wUoj05T0EpY0o8B59S9w5yeMWql7sw2kwNW1x4= 11 | github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= 12 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 13 | github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= 14 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 15 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 16 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 17 | github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= 18 | github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= 19 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 20 | github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 21 | github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= 22 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= 23 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 24 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 25 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 26 | github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= 27 | github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= 28 | github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= 29 | github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= 30 | github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 31 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 32 | github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= 33 | github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 34 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 35 | github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= 36 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 37 | github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= 38 | github.com/myesui/uuid v1.0.0 h1:xCBmH4l5KuvLYc5L7AS7SZg9/jKdIFubM7OVoLqaQUI= 39 | github.com/myesui/uuid v1.0.0/go.mod h1:2CDfNgU0LR8mIdO8vdWd8i9gWWxLlcoIGGpSNgafq84= 40 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 41 | github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= 42 | github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 43 | github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= 44 | github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 45 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 46 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 47 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 48 | github.com/twinj/uuid v1.0.0 h1:fzz7COZnDrXGTAOHGuUGYd6sG+JMq+AoE7+Jlu0przk= 49 | github.com/twinj/uuid v1.0.0/go.mod h1:mMgcE1RHFUFqe5AfiwlINXisXfDGro23fWdPUfOMjRY= 50 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 51 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 52 | github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= 53 | github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= 54 | github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 55 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 56 | golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 57 | golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 58 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 59 | golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= 60 | golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 61 | golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 62 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 63 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 64 | golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 65 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 66 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 67 | golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= 68 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 69 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 70 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 71 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 72 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 73 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 74 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 75 | golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 76 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 77 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 78 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 79 | golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6 h1:DvY3Zkh7KabQE/kfzMvYvKirSiguP9Q/veMtkYyf0o8= 80 | golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 81 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 82 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 83 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 84 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 85 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 86 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 87 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 88 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 89 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 90 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 91 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 92 | gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= 93 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 94 | -------------------------------------------------------------------------------- /infrastructure/persistence/db.go: -------------------------------------------------------------------------------- 1 | package persistence 2 | 3 | import ( 4 | "github.com/go-sql-driver/mysql" 5 | _ "github.com/go-sql-driver/mysql" 6 | "github.com/jinzhu/gorm" 7 | "github.com/lupguo/go-ddd-sample/domain/entity" 8 | "github.com/lupguo/go-ddd-sample/domain/repository" 9 | "time" 10 | ) 11 | 12 | // Repositories 总仓储机构提,包含多个领域仓储接口,以及一个DB实例 13 | type Repositories struct { 14 | UploadImg repository.UploadImgRepo 15 | db *gorm.DB 16 | } 17 | 18 | // NewRepositories 初始化所有域的总仓储实例,将实例通过依赖注入方式,将DB实例注入到领域层 19 | func NewRepositories(DbDriver, DbUser, DbPassword, DbPort, DbHost, DbName string) (*Repositories, error) { 20 | cfg := &mysql.Config{ 21 | User: DbUser, 22 | Passwd: DbPassword, 23 | Net: "tcp", 24 | Addr: DbHost + ":" + DbPort, 25 | DBName: DbName, 26 | Collation: "utf8mb4_general_ci", 27 | Loc: time.FixedZone("Asia/Shanghai", 8*60*60), 28 | Timeout: time.Second, 29 | ReadTimeout: 30 * time.Second, 30 | WriteTimeout: 30 * time.Second, 31 | AllowNativePasswords: true, 32 | ParseTime: true, 33 | } 34 | // DBSource := fmt.Sprintf("%s:%s@%s(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", DbUser, DbPassword, "tcp", DbHost, DbPort, DbName) 35 | db, err := gorm.Open(DbDriver, cfg.FormatDSN()) 36 | if err != nil { 37 | return nil, err 38 | } 39 | db.LogMode(true) 40 | 41 | // 初始化总仓储实例 42 | return &Repositories{ 43 | UploadImg: NewUploadImgPersis(db), 44 | db: db, 45 | }, nil 46 | } 47 | 48 | // closes the database connection 49 | func (s *Repositories) Close() error { 50 | return s.db.Close() 51 | } 52 | 53 | // This migrate all tables 54 | func (s *Repositories) AutoMigrate() error { 55 | return s.db.AutoMigrate(&entity.UploadImg{}).Error 56 | } 57 | -------------------------------------------------------------------------------- /infrastructure/persistence/uploadimg_persis.go: -------------------------------------------------------------------------------- 1 | // persistence 通过依赖注入方式,实现领域对持久化存储的控制反转(IOC) 2 | package persistence 3 | 4 | import ( 5 | "errors" 6 | "github.com/jinzhu/gorm" 7 | "github.com/lupguo/go-ddd-sample/domain/entity" 8 | ) 9 | 10 | // UploadImgPersis 上传图片的持久化结构体 11 | type UploadImgPersis struct { 12 | db *gorm.DB 13 | } 14 | 15 | // NewUploadImgPersis 创建上传图片DB存储实例 16 | func NewUploadImgPersis(db *gorm.DB) *UploadImgPersis { 17 | return &UploadImgPersis{db} 18 | } 19 | 20 | // Save 保存一张上传图片 21 | func (p *UploadImgPersis) Save(img *entity.UploadImg) (*entity.UploadImg, error) { 22 | err := p.db.Create(img).Error 23 | if err != nil { 24 | return nil, err 25 | } 26 | return img, nil 27 | } 28 | 29 | // Get 获取一张上传图片 30 | func (p *UploadImgPersis) Get(id uint64) (*entity.UploadImg, error) { 31 | var img entity.UploadImg 32 | err := p.db.Where("id = ?", id).Take(&img).Error 33 | if gorm.IsRecordNotFoundError(err) { 34 | return nil, errors.New("upload image not found") 35 | } 36 | if err != nil { 37 | return nil, err 38 | } 39 | return &img, nil 40 | } 41 | 42 | // GetAll 获取一组上传图片 43 | func (p *UploadImgPersis) GetAll() ([]entity.UploadImg, error) { 44 | var imgs []entity.UploadImg 45 | err := p.db.Limit(50).Order("created_at desc").Find(&imgs).Error 46 | if gorm.IsRecordNotFoundError(err) { 47 | return nil, errors.New("upload images not found") 48 | } 49 | if err != nil { 50 | return nil, err 51 | } 52 | return imgs, nil 53 | } 54 | 55 | // Delete 删除一张图片 56 | func (p *UploadImgPersis) Delete(id uint64) error { 57 | var img entity.UploadImg 58 | err := p.db.Where("id = ?", id).Delete(&img).Error 59 | if err != nil { 60 | return err 61 | } 62 | return nil 63 | } -------------------------------------------------------------------------------- /interfaces/api/handler/uploadimg_handler.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/labstack/echo" 7 | "github.com/lupguo/go-ddd-sample/application" 8 | "github.com/lupguo/go-ddd-sample/domain/entity" 9 | "io" 10 | "io/ioutil" 11 | "math/rand" 12 | "net/http" 13 | "os" 14 | "path" 15 | "strconv" 16 | "time" 17 | ) 18 | 19 | // UploadImgHandle 上传处理 20 | func UploadImgHandle(c echo.Context) error { 21 | callback := c.QueryParam("callback") 22 | var content struct { 23 | Response string `json:"response"` 24 | Timestamp time.Time `json:"timestamp"` 25 | Random int `json:"random"` 26 | } 27 | content.Response = "Sent via JSONP" 28 | content.Timestamp = time.Now().UTC() 29 | content.Random = rand.Intn(1000) 30 | return c.JSONP(http.StatusOK, callback, &content) 31 | } 32 | 33 | // UploadImgHandler 图片上传接口层处理 34 | type UploadImgHandler struct { 35 | uploadImgApp application.UploadImgAppIer 36 | } 37 | 38 | // NewUploadImgHandler 初始化一个图片上传接口 39 | func NewUploadImgHandler(app application.UploadImgAppIer) *UploadImgHandler { 40 | return &UploadImgHandler{uploadImgApp: app} 41 | } 42 | 43 | func (h *UploadImgHandler) Save(c echo.Context) error { 44 | forms, err := c.MultipartForm() 45 | if err != nil { 46 | return err 47 | } 48 | var imgs []*entity.UploadImg 49 | for _, file := range forms.File["upload"] { 50 | fo, err := file.Open() 51 | if err != nil { 52 | continue 53 | } 54 | // file storage path 55 | _, err = os.Stat(os.Getenv("IMAGE_STORAGE")) 56 | if err != nil { 57 | if os.IsNotExist(err) { 58 | if err := os.MkdirAll(os.Getenv("IMAGE_STORAGE"), 0755); err != nil { 59 | return err 60 | } 61 | } else { 62 | return err 63 | } 64 | } 65 | // file save 66 | ext := path.Ext(file.Filename) 67 | tempFile, err := ioutil.TempFile(os.Getenv("IMAGE_STORAGE"), "img_*"+ext) 68 | if err != nil { 69 | return err 70 | } 71 | _, err = io.Copy(tempFile, fo) 72 | if err != nil { 73 | return err 74 | } 75 | // upload 76 | uploadImg := entity.UploadImg{ 77 | Name: file.Filename, 78 | Path: tempFile.Name(), 79 | CreatedAt: time.Time{}, 80 | UpdatedAt: time.Time{}, 81 | } 82 | img, err := h.uploadImgApp.Save(&uploadImg) 83 | if err != nil { 84 | return err 85 | } 86 | imgs = append(imgs, img) 87 | } 88 | return c.JSON(http.StatusOK, imgs) 89 | } 90 | 91 | func (h *UploadImgHandler) Get(c echo.Context) error { 92 | strID := c.Param("id") 93 | if strID == "" { 94 | return errors.New("the input image ID is empty") 95 | } 96 | id, err := strconv.ParseUint(strID, 10, 0) 97 | if err != nil { 98 | return err 99 | } 100 | img, err := h.uploadImgApp.Get(id) 101 | if err != nil { 102 | return err 103 | } 104 | return c.JSON(http.StatusOK, img) 105 | } 106 | 107 | func (h *UploadImgHandler) GetAll(c echo.Context) error { 108 | imgs, err := h.uploadImgApp.GetAll() 109 | if err != nil { 110 | return err 111 | } 112 | return c.JSON(http.StatusOK, imgs) 113 | } 114 | 115 | func (h *UploadImgHandler) Delete(c echo.Context) error { 116 | strID := c.Param("id") 117 | if strID == "" { 118 | return errors.New("the deleted image ID is empty") 119 | } 120 | id, err := strconv.ParseUint(strID, 10, 0) 121 | if err != nil { 122 | return err 123 | } 124 | err = h.uploadImgApp.Delete(id) 125 | if err != nil { 126 | return err 127 | } 128 | msg := fmt.Sprintf(`{"msg": "delete Imgage ID:%s success"`, strID) 129 | return c.JSON(http.StatusOK, msg) 130 | } 131 | -------------------------------------------------------------------------------- /interfaces/api/middleware/middleware.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/labstack/echo" 5 | ) 6 | 7 | func AuthMiddleware() echo.HandlerFunc { 8 | // return func(c *gin.Context) { 9 | // err := auth.TokenValid(c.Request) 10 | // if err != nil { 11 | // c.JSON(http.StatusUnauthorized, gin.H{ 12 | // "status": http.StatusUnauthorized, 13 | // "error": err.Error(), 14 | // }) 15 | // c.Abort() 16 | // return 17 | // } 18 | // c.Next() 19 | // } 20 | return func(context echo.Context) error { 21 | return nil 22 | } 23 | } 24 | 25 | func CORSMiddleware() echo.HandlerFunc { 26 | // return func(c *gin.Context) { 27 | // c.Writer.Header().Set("Access-Control-Allow-Origin", "*") 28 | // c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") 29 | // 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") 30 | // c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, PATCH, DELETE") 31 | // 32 | // if c.Request.Method == "OPTIONS" { 33 | // c.AbortWithStatus(204) 34 | // return 35 | // } 36 | // c.Next() 37 | // } 38 | return func(context echo.Context) error { 39 | return nil 40 | } 41 | } 42 | 43 | // Avoid a large file from loading into memory 44 | // If the file size is greater than 8MB dont allow it to even load into memory and waste our time. 45 | func MaxSizeAllowed(n int64) echo.HandlerFunc { 46 | // return func(c *gin.Context) { 47 | // c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, n) 48 | // buff, errRead := c.GetRawData() 49 | // if errRead != nil { 50 | // //c.JSON(http.StatusRequestEntityTooLarge,"too large") 51 | // c.JSON(http.StatusRequestEntityTooLarge, gin.H{ 52 | // "status": http.StatusRequestEntityTooLarge, 53 | // "upload_err": "too large: upload an image less than 8MB", 54 | // }) 55 | // c.Abort() 56 | // return 57 | // } 58 | // buf := bytes.NewBuffer(buff) 59 | // c.Request.Body = ioutil.NopCloser(buf) 60 | // } 61 | return func(context echo.Context) error { 62 | return nil 63 | } 64 | } -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 |