原作者:https://github.com/congz666/cmall-go

Go实战-Gin-mall经典电子商城项目

视频地址:https://www.bilibili.com/video/BV1Zd4y1U7D8/

待完善:

Elasticsearch

redis查询商品 + 缓存

接口文件目录:

一、项目创建与读取配置文件

项目结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
gin_mall
├─api Controller层
├─cache 存放redis缓存
├─cmd
└─ main.go main启动类
├─conf 存放配置文件
├─dao 持久层(对数据库的操作)
├─middleware 中间件
├─model 存放数据库模型
├─pkg 工具包
├─routes 存放路由
├─serializer JSON序列化工具
└─service 服务层业务逻辑

项目使用的所有驱动(依赖)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 可以使用 go mod tidy 一键导入所有驱动

# ini配置文件读取驱动
go get gopkg.in/ini.v1
# gorm框架
go get gorm.io/gorm
# gin框架
go get github.com/gin-gonic/gin
# mysql驱动
go get gorm.io/driver/mysql
# mysql配置主从的插件
go get gorm.io/plugin/dbresolver
# jwt鉴权
go get github.com/dgrijalva/jwt-go
# 发送邮件的依赖
go get gopkg.in/mail.v2
# 日志框架驱动
go get github.com/sirupsen/logrus
# redis驱动
go get github.com/go-redis/redis

这一部分和todolist的配置差不多,①创建并读取配置文件,②配置并连接mysql数据库,额外添加mysql主从配置

①设置配置文件config.ini

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
[service]
AppMode = debug
HttpPort = :4000

[mysql]
Db = mysql
DbHost = 127.0.0.1
DbPort = 3306
DbUser = root
DbPassWord = 123456
DbName = gin_mall

[redis]
RedisDb = redis
RedisAddr = 127.0.0.1:6379
RedisPw =
RedisDbName = 2

[path]
Host = http://127.0.0.1
ProductPath = /static/imgs/product/
AvatarPath = /static/imgs/avatar/

[email]
ValidEmail=http://localhost:8080/#/vaild/email/
SmtpHost = smtp.qq.com
SmtpEmail = 你的邮箱
SmtpPass = 你的邮箱的通信码

②读取配置文件

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
// conf.config
package conf

import (
"example.com/unicorn-acc/dao"
"gopkg.in/ini.v1"
"strings"
)

var (
AppMode string
HttpPort string

Db string
DbHost string
DbPort string
DbUser string
DbPassWord string
DbName string

ValidEmail string
SmtpHost string
SmtpEmail string
SmtpPass string

PhotoHost string
ProductPhotoPath string
AvatarPath string
)

func Init() {
file, err := ini.Load("./conf/config.ini")
if err != nil {
panic(err)
}
LoadServer(file)
LoadMysqlData(file)
LoadEmail(file)
LoadPhotoPath(file)
// MySQL 读 主 // 方便以后拓展主从复制
pathRead := strings.Join([]string{DbUser, ":", DbPassWord, "@tcp(", DbHost, ":", DbPort, ")/", DbName, "?charset=utf8&parseTime=true"}, "")
// MySQL 写 从
pathWrite := strings.Join([]string{DbUser, ":", DbPassWord, "@tcp(", DbHost, ":", DbPort, ")/", DbName, "?charset=utf8&parseTime=true"}, "")
dao.Database(pathRead, pathWrite)
}

func LoadServer(file *ini.File) {
AppMode = file.Section("service").Key("AppMode").String()
HttpPort = file.Section("service").Key("HttpPort").String()
}

func LoadMysqlData(file *ini.File) {
Db = file.Section("mysql").Key("Db").String()
DbHost = file.Section("mysql").Key("DbHost").String()
DbPort = file.Section("mysql").Key("DbPort").String()
DbUser = file.Section("mysql").Key("DbUser").String()
DbPassWord = file.Section("mysql").Key("DbPassWord").String()
DbName = file.Section("mysql").Key("DbName").String()
}

func LoadEmail(file *ini.File) {
ValidEmail = file.Section("email").Key("ValidEmail").String()
SmtpHost = file.Section("email").Key("SmtpHost").String()
SmtpEmail = file.Section("email").Key("SmtpEmail").String()
SmtpPass = file.Section("email").Key("SmtpPass").String()
}

func LoadPhotoPath(file *ini.File) {
PhotoHost = file.Section("path").Key("Host").String()
ProductPhotoPath = file.Section("path").Key("ProductPath").String()
AvatarPath = file.Section("path").Key("AvatarPath").String()
}

③建立数据库连接

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
//dao.init
package dao

import (
"context"
"github.com/gin-gonic/gin"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
"gorm.io/plugin/dbresolver"
"time"
)

var _db *gorm.DB

func Database(connRead, connWrite string) {
var ormLogger logger.Interface
// 设置日志等级
if gin.Mode() == "debug" {
ormLogger = logger.Default.LogMode(logger.Info)
} else {
ormLogger = logger.Default
}
// 创建mysql驱动并链接mysql数据库
db, err := gorm.Open(mysql.New(mysql.Config{ // 进行mysql的配置
DSN: connRead, // mysql主数据库
DefaultStringSize: 256, // string 类型字段的默认长度
DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
SkipInitializeWithVersion: false, // 根据版本自动配置
}), &gorm.Config{ // 进行gorm的配置
Logger: ormLogger,
NamingStrategy: schema.NamingStrategy{
SingularTable: true, // 数据表命名策略,不要加s
},
})
if err != nil {
panic(err)
}
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(20) //设置连接池,空闲
sqlDB.SetMaxOpenConns(100) //最大连接数
sqlDB.SetConnMaxLifetime(time.Second * 30) //最大连接时长
_db = db

//进行主从的配置 , 导入插件: go get gorm.io/plugin/dbresolver
_ = _db.Use(dbresolver.
Register(dbresolver.Config{
// `db2` 作为 sources,`db3`、`db4` 作为 replicas
Sources: []gorm.Dialector{mysql.Open(connRead)}, // 写操作
Replicas: []gorm.Dialector{mysql.Open(connWrite), mysql.Open(connWrite)}, // 读操作
Policy: dbresolver.RandomPolicy{}, // sources/replicas 负载均衡策略
}))
Migration() // 实现数据库的迁移
}

// NewDBClient 封装db连接,只暴露访问db连接的方法
func NewDBClient(ctx context.Context) *gorm.DB {
db := _db
return db.WithContext(ctx)
}

④实现数据库的迁移

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// dao.migration
package dao

import (
"fmt"
"os"
)

// Migration 执行数据迁移
func Migration() {
//自动迁移模式
err := _db.Set("gorm:table_options", "charset=utf8mb4").
AutoMigrate()
if err != nil {
fmt.Println("register table fail")
os.Exit(0)
}
fmt.Println("register table success")
}

⑤main方法调用配置文件读取

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

import (
"example.com/unicorn-acc/conf"
"fmt"
)

func main() {
conf.Init()
fmt.Println("helloworld")
}

二、数据库创建

在model中创建所有结构体(数据库表),然后使用gorm的自动迁移生成表

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
// 用户地址表
type Address struct {
gorm.Model
UserID uint `gorm:"not null"`
Name string `gorm:"type:varchar(20) not null"`
Phone string `gorm:"type:varchar(11) not null"`
Address string `gorm:"type:varchar(50) not null"`
}

// 用户登录相关信息
type Admin struct {
gorm.Model
UserName string
PasswordDigest string
Avatar string `gorm:"size:1000"`
}

// 用户分页相关
type BasePage struct {
PageNum int `form:"pageNum"`
PageSize int `form:"pageSize"`
}

// 轮播图
type Carousel struct {
gorm.Model
ImgPath string
ProductID uint `gorm:"not null"`
}

//购物车模型
type Cart struct {
gorm.Model
UserID uint
ProductID uint `gorm:"not null"`
BossID uint
Num uint
MaxNum uint
Check bool
}

// 商品分类
type Category struct {
gorm.Model
CategoryName string
}

// 收藏夹
type Favorite struct {
gorm.Model
User User `gorm:"ForeignKey:UserID"`
UserID uint `gorm:"not null"`
Product Product `gorm:"ForeignKey:ProductID"`
ProductID uint `gorm:"not null"`
Boss User `gorm:"ForeignKey:BossID"`
BossID uint `gorm:"not null"`
}

//User 用户模型
type User struct {
gorm.Model
UserName string `gorm:"unique"`
Email string
PasswordDigest string
NickName string
Status string
Avatar string `gorm:"size:1000"`
Money string
}

//商品模型
type Product struct {
gorm.Model
Name string `gorm:"size:255;index"`
CategoryID uint `gorm:"not null"`
Title string
Info string `gorm:"size:1000"`
ImgPath string
Price string
DiscountPrice string
OnSale bool `gorm:"default:false"`
Num int
BossID uint
BossName string
BossAvatar string
}

// 商品图片
type ProductImg struct {
gorm.Model
ProductID uint `gorm:"not null"`
ImgPath string
}

// Notice 公告模型 存放公告和邮件模板
type Notice struct {
gorm.Model
Text string `gorm:"type:text"`
}

//Order 订单信息
type Order struct {
gorm.Model
UserID uint `gorm:"not null"`
ProductID uint `gorm:"not null"`
BossID uint `gorm:"not null"`
AddressID uint `gorm:"not null"`
Num int // 数量
OrderNum uint64 // 订单号
Type uint // 1 未支付 2 已支付
Money float64
}

在gorm迁移中开启自动迁移

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package dao

// Migration 执行数据迁移
func Migration() {
//自动迁移模式
err := _db.Set("gorm:table_options", "charset=utf8mb4").
AutoMigrate(&model.User{},
&model.Product{},
&model.Carousel{},
&model.Category{},
&model.Favorite{},
&model.ProductImg{},
&model.Order{},
&model.Cart{},
&model.Admin{},
&model.Address{},
&model.Notice{})
if err != nil {
fmt.Println("register table fail")
os.Exit(0)
}
fmt.Println("register table success")
}

四、新建路由

新建路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// routes.router.go
package routes

func NewRouter() *gin.Engine {
r := gin.Default()
r.Use(middleware.Cors()) // 使用自己写的跨域中间件
r.StaticFS("/static", http.Dir("./static")) // 配置加载静态资源路径
v1 := r.Group("api/v1") // 新建路由组
{
v1.GET("ping", func(c *gin.Context) {
c.JSON(200, "pong")
})
}
return r
}

中间使用了一个跨域中间件

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
// cors.go
package middleware

// 跨域
func Cors() gin.HandlerFunc {
return func(c *gin.Context) {
method := c.Request.Method //请求方法
origin := c.Request.Header.Get("Origin") //请求头部
var headerKeys []string // 声明请求头keys
for k := range c.Request.Header {
headerKeys = append(headerKeys, k)
}
headerStr := strings.Join(headerKeys, ", ")
if headerStr != "" {
headerStr = fmt.Sprintf("access-control-allow-origin, access-control-allow-headers, %s", headerStr)
} else {
headerStr = "access-control-allow-origin, access-control-allow-headers"
}
if origin != "" {
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Origin", "*") // 这是允许访问所有域
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE") //服务器支持的所有跨域请求的方法,为了避免浏览次请求的多次'预检'请求
// header的类型
c.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, X-CSRF-Token, Token,session,X_Requested_With,Accept, Origin, Host, Connection, Accept-Encoding, Accept-Language,DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Pragma")
// 允许跨域设置 可以返回其他子段
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,FooBar") // 跨域关键设置 让浏览器可以解析
c.Header("Access-Control-Max-Age", "172800") // 缓存请求信息 单位为秒
c.Header("Access-Control-Allow-Credentials", "false") // 跨域请求是否需要带cookie信息 默认设置为true
c.Set("content-type", "application/json") // 设置返回格式是json
}
//放行所有OPTIONS方法
if method == "OPTIONS" {
c.JSON(http.StatusOK, "Options Request!")
}
// 处理请求
c.Next() // 处理请求
}
}

五、添加日志模块

pkg.utils.logger

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
package utils

import (
"github.com/sirupsen/logrus"
"log"
"os"
"path"
"time"
)

// 定义一个全局日志
var LogrusObj *logrus.Logger

func init() {
if LogrusObj != nil {
src, _ := setOutputFile()
//设置输出
LogrusObj.Out = src
return
}
//实例化
logger := logrus.New()
src, _ := setOutputFile()
//设置输出
logger.Out = src
//设置日志级别
logger.SetLevel(logrus.DebugLevel)
//设置日志格式
logger.SetFormatter(&logrus.TextFormatter{
TimestampFormat: "2006-01-02 15:04:05",
})
/*
加个hook形成ELK体系
但是考虑到一些同学一下子接受不了那么多技术栈,
所以这里的ELK体系加了注释,如果想引入可以直接注释去掉,
如果不想引入这样注释掉也是没问题的。
*/
//hook := model.EsHookLog()
//logger.AddHook(hook)
LogrusObj = logger
}


func setOutputFile() (*os.File, error) {
now := time.Now()
logFilePath := ""
if dir, err := os.Getwd(); err == nil {
logFilePath = dir + "/logs/"
}
_, err := os.Stat(logFilePath)
if os.IsNotExist(err) {
if err := os.MkdirAll(logFilePath, 0777); err != nil {
log.Println(err.Error())
return nil, err
}
}
logFileName := now.Format("2006-01-02") + ".log"
//日志文件
fileName := path.Join(logFilePath, logFileName)
if _, err := os.Stat(fileName); err != nil {
if _, err := os.Create(fileName); err != nil {
log.Println(err.Error())
return nil, err
}
}
//写入文件
src, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
if err != nil {
log.Println(err)
return nil, err
}
return src, nil
}

六、将error转换为json格式

  • api/v1/common.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
package v1

import (
"example.com/unicorn-acc/conf"
"example.com/unicorn-acc/serializer"
"fmt"
"github.com/go-playground/validator/v10"
"github.com/goccy/go-json"
)
// ErrorResponse 因为error不是json格式,在contorller层发送错误返回错误信息时需要返回json格式的err
func ErrorResponse(err error) serializer.Response {
if ve, ok := err.(validator.ValidationErrors); ok {
for _, e := range ve {
field := conf.T(fmt.Sprintf("Field.%s", e.Field))
tag := conf.T(fmt.Sprintf("Tag.Valid.%s", e.Tag))
return serializer.Response{
Status: 400,
Msg: fmt.Sprintf("%s%s", field, tag),
Error: fmt.Sprint(err),
}
}
}
if _, ok := err.(*json.UnmarshalTypeError); ok {
return serializer.Response{
Status: 400,
Msg: "JSON类型不匹配",
Error: fmt.Sprint(err),
}
}

return serializer.Response{
Status: 400,
Msg: "参数错误",
Error: fmt.Sprint(err),
}
}

调用举例:

1
2
3
4
5
6
7
8
if err := c.ShouldBind(&createProductService); err == nil {
res := createProductService.Create(c.Request.Context(), claim.ID, files)
c.JSON(200, res)
} else {
// 接口参数绑定异常就返回json格式的err
c.JSON(400, ErrorResponse(err))
utils.LogrusObj.Infoln(err)
}

六、配置引入Redis

①读取Redis配置,②进行Redis链接

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
// cache.common.go
package cache

import (
"fmt"
"github.com/go-redis/redis"
"gopkg.in/ini.v1"
"strconv"
)

// RedisClient Redis缓存客户端单例
var (
RedisClient *redis.Client
RedisDb string
RedisAddr string
RedisPw string
RedisDbName string
)

// Redis 在中间件中初始化redis链接 防止循环导包,所以放在这里
func init() {
file, err := ini.Load("./conf/config.ini")
if err != nil {
fmt.Println("配置文件读取错误,请检查文件路径:", err)
}
LoadRedisData(file)
Redis()
}

// Redis 在中间件中初始化redis链接
func Redis() {
db, _ := strconv.ParseUint(RedisDbName, 10, 64)
client := redis.NewClient(&redis.Options{
Addr: RedisAddr,
Password: RedisPw,
DB: int(db),
})
// 心跳检测,如果有err就panic
_, err := client.Ping().Result()
if err != nil {
fmt.Println(err)
panic(err)
}
RedisClient = client
}

func LoadRedisData(file *ini.File) {
RedisDb = file.Section("redis").Key("RedisDb").String()
RedisAddr = file.Section("redis").Key("RedisAddr").String()
RedisPw = file.Section("redis").Key("RedisPw").String()
RedisDbName = file.Section("redis").Key("RedisDbName").String()
}

以获取商品点击数为例:

①创建Key:

1
2
3
4
5
6
7
8
9
10
// cache.key.go
const (
//RankKey 每日排名
RankKey = "rank"
)

func ProductViewKey(id uint) string {
return fmt.Sprintf("view:product:%s", strconv.Itoa(int(id)))
}

  • 下面这两个方法写在model的模型结构中(model.product.go)

②获取点击数

1
2
countStr, _ := cache.RedisClient.Get(cache.ProductViewKey(product.ID)).Result()
count, _ := strconv.ParseUint(countStr, 10, 64)

③增加点击数

1
2
countStr, _ := cache.RedisClient.Get(cache.ProductViewKey(product.ID)).Result()
count, _ := strconv.ParseUint(countStr, 10, 64)

接下来都是编写接口了

这次除了用户注册不写具体的包了,各别包会标注一下

一、用户操作

1、用户注册

① 首先编写路由routes.router.go

1
v1.POST("user/register", api.UserRegister)

② Controller层:在api.v1.user.go中编写方法

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

// UserRegister 这个相当于Controller层
func UserRegister(c *gin.Context) {
// 实现一个UserService结构体,调用该Service的方法
var userRegister service.UserService
if err := c.ShouldBind(&userRegister); err == nil {
res := userRegister.Register(c.Request.Context())
c.JSON(http.StatusOK, res)
} else {
c.JSON(http.StatusBadRequest, err)
}
}

③Service层:service.user.go

1、创建一个UserService结构体

2、实现注册方法

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 service

import (
"context"
"example.com/unicorn-acc/dao"
"example.com/unicorn-acc/model"
"example.com/unicorn-acc/pkg/e"
"example.com/unicorn-acc/pkg/utils"
"example.com/unicorn-acc/serializer"
)

// UserService 管理用户服务
type UserService struct {
NickName string `form:"nick_name" json:"nick_name"`
UserName string `form:"user_name" json:"user_name"`
Password string `form:"password" json:"password"`
Key string `form:"key" json:"key"` // 前端进行判断
}

func (service UserService) Register(ctx context.Context) serializer.Response {
var user *model.User
code := e.SUCCESS
// service.Key是密钥
if service.Key == "" || len(service.Key) != 16 {
code = e.ERROR
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
Data: "密钥长度不足",
}
}
// 进行对称加密 ==> 密文存储 对称加密操作
utils.Encrypt.SetKey(service.Key)
// todo 创建一个userdao对象,(将与数据库操作的部分都在DAO层进行处理)
userDao := dao.NewUserDao(ctx)
// 1. 判断当前用户名是否被注册了
_, exist, err := userDao.ExistOrNotByUserName(service.UserName)
if err != nil {
code = e.ErrorDatabase
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
}
}
if exist {
code = e.ErrorExistUser
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
}
}
// 注册的用户符合条件
user = &model.User{
NickName: service.NickName,
UserName: service.UserName,
Status: model.Active,
Avatar: "avatar.JPG", // 默认头像
Money: utils.Encrypt.AesEncoding("10000"), // 初始金额的encoding

}
// 2. 加密密码
if err = user.SetPassword(service.Password); err != nil {
code = e.ErrorFailEncryption
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
}
}
// 3. 创建用户
err = userDao.CreateUser(user)
if err != nil {
code = e.ERROR
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
}
}
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
}
}

userDao := dao.NewUserDao(ctx)

  • 将所有关于数据库操作的都封装在dao中,操作方式也是新建一个dao对象,然后调用其方法

② 将返回状态码和返回信息封装起来,封装在pkg/e包下,统一管理,返回信息可以根据返回状态码返回

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
// e.go
// 存放所有状态码信息
const (
// 通用错误码
SUCCESS = 200
UpdatePasswordSuccess = 201
NotExistInentifier = 202
ERROR = 500
InvalidParams = 400

...
// 数据库错误
ErrorDatabase = 40001
)

// msg.go

// 定义所有错误信息
var MsgFlags = map[int]string{
SUCCESS: "ok",
UpdatePasswordSuccess: "修改密码成功",
NotExistInentifier: "该第三方账号未绑定",
ERROR: "fail",
InvalidParams: "请求参数错误",

...
ErrorDatabase: "数据库操作出错,请重试",
}

// GetMsg 获取状态码对应信息
func GetMsg(code int) string {
msg, ok := MsgFlags[code]
if ok {
return msg
}
return MsgFlags[ERROR]
}

③构建通用返回类:

1
2
3
4
5
6
7
// 通用返回类(基础序列化器)
type Response struct {
Status int `json:"status"`
Data interface{} `json:"data"`
Msg string `json:"msg"`
Error string `json:"error"`
}

④DAO层:与数据库操作的方法

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
package dao

import (
"context"
"example.com/unicorn-acc/model"
"gorm.io/gorm"
)

type UserDao struct {
*gorm.DB
}

func NewUserDao(ctx context.Context) *UserDao {
return &UserDao{NewDBClient(ctx)}
}

// ExistOrNotByUserName 根据username判断是否存在该名字
func (dao *UserDao) ExistOrNotByUserName(username string) (user *model.User, exist bool, err error) {
// 1. find查看一下是否存在该名字
var count int64
err = dao.DB.Model(&model.User{}).Where("user_name = ?", username).Find(&user).Count(&count).Error
if count == 0 {
return nil, false, err
}
return user, true, nil
}

// 创建用户
func (dao *UserDao) CreateUser(user *model.User) (err error) {
return dao.DB.Model(&model.User{}).Create(&user).Error
}

新建DAO对象中复用了DB连接,创建对象后,根据dao对象.db进行访问

1
2
3
4
5
// NewDBClient 封装db连接,只暴露访问db连接的方法
func NewDBClient(ctx context.Context) *gorm.DB {
db := _db
return db.WithContext(ctx)
}

2、用户登录

1
v1.POST("user/login", api.UserLogin)
1
2
3
4
5
6
7
8
9
10
// controller
func UserLogin(c *gin.Context) {
var userLoginService service.UserService
if err := c.ShouldBind(&userLoginService); err == nil {
res := userLoginService.Login(c.Request.Context())
c.JSON(http.StatusOK, res)
} else {
c.JSON(http.StatusBadRequest, err)
}
}
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
// service
func (service *UserService) Login(ctx context.Context) serializer.Response {
var user *model.User
code := e.SUCCESS
userDao := dao.NewUserDao(ctx)
// 1.查看用户是否存在
user, exist, err := userDao.ExistOrNotByUserName(service.UserName)
if !exist { //如果查询不到,返回相应的错误
code = e.ErrorUserNotFound
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
}
}
// 2.用户存在,查看密码输入是否正确
// 加密后进行对比
if user.CheckPassword(service.Password) == false {
code = e.ErrorNotCompare
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
}
}
// 3. 认证通过,生成token(JWT签发token)
token, err := utils.GenerateToken(user.ID, service.UserName, 0)
if err != nil {
code = e.ErrorAuthToken
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
}
}
// 成功,返回token
return serializer.Response{
Status: code,
Data: serializer.TokenData{User: serializer.BuildUser(user), Token: token},
Msg: e.GetMsg(code),
}
}

token的签发:F:-gin_ mall_mall.go

成功返回的用户json序列器:

1
2
3
4
5
// TokenData 带有token的Data结构
type TokenData struct {
User interface{} `json:"user"`
Token string `json:"token"`
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type User struct { // vo ;view object  传给视图的对象
ID uint `json:"id"`
UserName string `json:"user_name"`
NickName string `json:"nickname"`
Type int `json:"type"`
Email string `json:"email"`
Status string `json:"status"`
Avatar string `json:"avatar"`
CreateAt int64 `json:"create_at"`
}

//BuildUser 序列化用户
func BuildUser(user *model.User) User {
return User{
ID: user.ID,
UserName: user.UserName,
NickName: user.NickName,
Email: user.Email,
Status: user.Status,
Avatar: conf.PhotoHost + conf.HttpPort + conf.AvatarPath + user.AvatarURL(),
CreateAt: user.CreatedAt.Unix(),
}
}
1
2
3
4
5
6
//dao
// CheckPassword 校验密码
func (user *User) CheckPassword(password string) bool {
err := bcrypt.CompareHashAndPassword([]byte(user.PasswordDigest), []byte(password))
return err == nil
}


3、用户修改

JWT验权(验证用户是否登录)

JWT验权:

1
2
3
4
5
6
authed := v1.Group("/") // 需要登陆保护
authed.Use(middleware.JWT())
{
// 用户操作
authed.PUT("user", api.UserUpdate)
}

middleware.JWT()

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
// JWT token验证中间件
func JWT() gin.HandlerFunc {
return func(c *gin.Context) {
var code int
var data interface{}
code = 200
token := c.GetHeader("Authorization")
if token == "" {
code = 404
} else {
claims, err := utils.ParseToken(token)
if err != nil {
code = e.ErrorAuthCheckTokenFail
} else if time.Now().Unix() > claims.ExpiresAt {
code = e.ErrorAuthCheckTokenTimeout
}
}
if code != e.SUCCESS {
c.JSON(200, gin.H{
"status": code,
"msg": e.GetMsg(code),
"data": data,
})
c.Abort()
return
}
c.Next()
}
}

utils.jwt工具类:

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
var jwtSecret = []byte("SecretKey")

type Claims struct {
ID uint `json:"id"`
Username string `json:"username"`
Authority int `json:"authority"`
jwt.StandardClaims
}

// GenerateToken 签发用户Token
func GenerateToken(id uint, username string, authority int) (string, error) {
nowTime := time.Now()
expireTime := nowTime.Add(24 * time.Hour)
claims := Claims{
ID: id,
Username: username,
Authority: authority,
StandardClaims: jwt.StandardClaims{
ExpiresAt: expireTime.Unix(),
Issuer: "mall",
},
}
tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
token, err := tokenClaims.SignedString(jwtSecret)
return token, err
}

// ParseToken 验证用户token
func ParseToken(token string) (*Claims, error) {
tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return jwtSecret, nil
})
if tokenClaims != nil {
if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid {
return claims, nil
}
}
return nil, err
}

用户修改

1
2
3
4
5
6
7
8
9
10
11
func UserUpdate(c *gin.Context) {
var userUpdateService service.UserService
// 在这里能通过解析token得到用户的信息,包括用户id
claims, _ := utils.ParseToken(c.GetHeader("Authorization"))
if err := c.ShouldBind(&userUpdateService); err == nil {
res := userUpdateService.Update(c.Request.Context(), claims.ID)
c.JSON(http.StatusOK, res)
} else {
c.JSON(http.StatusBadRequest, err)
}
}
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
func (service *UserService) Update(ctx context.Context, uid uint) serializer.Response {
var user *model.User
var err error
code := e.SUCCESS
// 1. 查找用户
userDao := dao.NewUserDao(ctx)
user, err = userDao.GetUserById(uid)
if service.NickName != "" {
user.NickName = service.NickName
}

// 2.更新用户
err = userDao.UpdateUserById(uid, user)
if err != nil {
code = e.ErrorDatabase
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
Error: err.Error(),
}
}
return serializer.Response{
Status: code,
Data: serializer.BuildUser(user),
Msg: e.GetMsg(code),
}
}
1
2
3
4
5
6
7
8
9
10
11
func (dao *UserDao) GetUserById(id uint) (user *model.User, err error) {
err = dao.DB.Model(&model.User{}).Where("id = ?", id).First(&user).Error
if err != nil {
return nil, err
}
return
}

func (dao *UserDao) UpdateUserById(id uint, user *model.User) error {
return dao.DB.Model(&model.User{}).Where("id = ?", id).Updates(&user).Error
}

4、更换头像

1
authed.POST("avatar", api.UploadAvatar)
1
2
3
4
5
6
7
8
9
10
11
12
13
func UploadAvatar(c *gin.Context) {
// 从请求表单中获取头像
file, fileHeader, _ := c.Request.FormFile("file")
filesize := fileHeader.Size
uploadAvatarService := service.UserService{}
claims, _ := utils.ParseToken(c.GetHeader("Authorization"))
if err := c.ShouldBind(&uploadAvatarService); err == nil {
res := uploadAvatarService.Post(c.Request.Context(), claims.ID, file, filesize)
c.JSON(http.StatusOK, res)
} else {
c.JSON(http.StatusBadRequest, err)
}
}
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
func (service *UserService) Post(ctx context.Context, uid uint, file multipart.File, filesize int64) serializer.Response {
code := e.SUCCESS
var user *model.User
var err error
userDao := dao.NewUserDao(ctx)
// 1.查看用户是否存在
user, err = userDao.GetUserById(uid)
if err != nil {
code = e.ErrorDatabase
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
Error: err.Error(),
}
}
// 2. 保存图片到本地路径,返回路径
path, err := UploadAvatarToLocalStatic(file, uid, user.UserName)
if err != nil {
code = e.ErrorUploadFile
return serializer.Response{
Status: code,
Data: e.GetMsg(code),
Error: path,
}
}
// 3. 保存用户信息
// 复用之前写的接口
user.Avatar = path
err = userDao.UpdateUserById(uid, user)
if err != nil {
code = e.ErrorDatabase
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
Error: err.Error(),
}
}
return serializer.Response{
Status: code,
Data: serializer.BuildUser(user),
Msg: e.GetMsg(code),
}
}

上传文件(JPG)

文件上传:创建一个新的service.upload.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
func UploadAvatarToLocalStatic(file multipart.File, userId uint, username string) (filepath string, err error) {
bId := strconv.Itoa(int(userId)) // int => string
basePath := "." + conf.AvatarPath + "user" + bId + "/"
// 如果路径不存在,则创建路径
if !DirExistOrNot(basePath) {
CreateDir(basePath)
}
avatarPath := basePath + username + ".jpg"
content, err := io.ReadAll(file)
if err != nil {
return "", err // 读取失败
}
err = os.WriteFile(avatarPath, content, 0666)
if err != nil {
return "", err
}
return "user" + bId + "/" + username + ".jpg", err
}

// DirExistOrNot 判断文件是否存在
func DirExistOrNot(fileAddr string) bool {
s, err := os.Stat(fileAddr)
if err != nil {
log.Println(err)
return false
}
return s.IsDir()
}

// CreateDir 创建文件夹
func CreateDir(dirName string) bool {
err := os.MkdirAll(dirName, 755)
if err != nil {
log.Println(err)
return false
}
return true
}


5、绑定邮箱(向用户发送邮件)

配置发送邮件的邮箱

QQ邮箱 => 设置 => 账户 => 开启POP3/SMTP服务

  • config中SmtpEmail是发送邮箱,也就是用这个邮箱发给用户
1
2
3
4
5
[email]
ValidEmail=http://localhost:8080/#/vaild/email/
SmtpHost = smtp.qq.com
SmtpEmail = 1049236690@qq.com
SmtpPass = 你的邮箱的通信码

在用户绑定邮箱的时候需要重新生成一个token,这个token携带了email信息,在验证email的时候需要验证该token

1
2
3
4
5
6
7
8
9
10
11
12
13
// EmailClaims
type EmailClaims struct {
UserID uint `json:"user_id"`
Email string `json:"email"`
Password string `json:"password"`
OperationType uint `json:"operation_type"`
jwt.StandardClaims
}

// GenerateEmailToken 签发邮箱验证Token
func GenerateEmailToken(userID, Operation uint, email, password string) (string, error) {
...
}

绑定邮箱请求:(发送邮件进行确认)

1
authed.POST("user/sending-email", api.SendEmail)
1
2
3
4
5
6
7
8
9
10
func SendEmail(c *gin.Context) {
var sendEmailService service.SendEmailService
claim, _ := utils.ParseToken(c.GetHeader("Authorization"))
if err := c.ShouldBind(&sendEmailService); err == nil {
res := sendEmailService.Send(c.Request.Context(), claim.ID)
c.JSON(200, res)
} else {
c.JSON(http.StatusBadRequest, err)
}
}
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
func (service *SendEmailService) Send(ctx context.Context, uid uint) serializer.Response {
code := e.SUCCESS
var address string
var notice *model.Notice // ①绑定邮箱 ②修改密码,都需要模板通知
// 1. 重新生成一个emailtoken给用户
token, err := utils.GenerateEmailToken(uid, service.OperationType, service.Email, service.Password)
if err != nil {
code = e.ErrorAuthToken
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
}
}
// 2. 获取邮箱模板
noticeDao := dao.NewNoticeDao(ctx)
notice, err = noticeDao.GetNoticeById(service.OperationType)
if err != nil {
code = e.ErrorDatabase
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
Error: err.Error(),
}
}
// 3.创建一个新的邮件并发送
address = conf.ValidEmail + token // 发送方
// 对获取到的模板 进行内容的替换
mailStr := notice.Text
mailText := strings.Replace(mailStr, "Email", address, -1)
// 导入依赖go get gopkg.in/mail.v2, 对邮件进行填充
m := mail.NewMessage()
m.SetHeader("From", conf.SmtpEmail)
m.SetHeader("To", service.Email)
m.SetHeader("Subject", "subject")
m.SetBody("text/html", mailText)
d := mail.NewDialer(conf.SmtpHost, 465, conf.SmtpEmail, conf.SmtpPass)
d.StartTLSPolicy = mail.MandatoryStartTLS
if err := d.DialAndSend(m); err != nil {
code = e.ErrorSendEmail
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
}
}
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
}
}

关于邮件模板notice的dao和user.dao相似

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package dao

type NoticeDao struct {
*gorm.DB
}

func NewNoticeDao(ctx context.Context) *NoticeDao {
return &NoticeDao{NewDBClient(ctx)}
}

// GetNoticeById 通过id获取notice
func (dao *NoticeDao) GetNoticeById(id uint) (notice *model.Notice, err error) {
err = dao.DB.Model(&model.Notice{}).Where("id = ?", id).First(&notice).Error
return notice, err
}

6、验证邮箱(绑定邮箱、解绑邮箱、修改密码)

在上面绑定邮箱请求后,服务器会向用户邮箱发送邮件进行确认,用户点击生成的链接即触发验证邮箱

1
authed.POST("user/valid-email", api.ValidEmail)
1
2
3
4
5
6
7
8
9
func ValidEmail(c *gin.Context) {
var validEmailService service.ValidEmailService
if err := c.ShouldBind(&validEmailService); err == nil {
res := validEmailService.Valid(c.Request.Context(), c.GetHeader("Authorization"))
c.JSON(200, res)
} else {
c.JSON(http.StatusBadRequest, err)
}
}
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
type ValidEmailService struct {
}

// Valid 验证邮箱
func (service *ValidEmailService) Valid(ctx context.Context, token string) serializer.Response {
var userID uint
var email string
var password string
var operationType uint
code := e.SUCCESS

// 1.验证token
if token == "" {
code = e.InvalidParams
} else {
claims, err := utils.ParseEmailToken(token)
if err != nil {
code = e.ErrorAuthCheckTokenFail
} else if time.Now().Unix() > claims.ExpiresAt {
code = e.ErrorAuthCheckTokenTimeout
} else {
userID = claims.UserID
email = claims.Email
password = claims.Password
operationType = claims.OperationType
}
}
// token有问题
if code != e.SUCCESS {
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
}
}
// 2. 用户token信息无误,进行对应操作
userDao := dao.NewUserDao(ctx)
user, err := userDao.GetUserById(userID)
if err != nil {
return serializer.Result(e.ErrorDatabase)
}

if operationType == 1 {
// 1 : 绑定邮箱
user.Email = email
} else if operationType == 2 {
// 2 : 解绑邮箱
user.Email = ""
} else if operationType == 3 {
// 3 : 修改密码
err = user.SetPassword(password)
if err != nil {
return serializer.Result(e.ErrorDatabase)
}
}
// 3. 保存用户信息
err = userDao.UpdateUserById(userID, user)
if err != nil {
return serializer.Result(e.ErrorDatabase)
}

// 4.成功,返回用户信息
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
Data: serializer.BuildUser(user),
}

}

绑定邮箱结果:

7、获取用户金额

因为数据库中的钱是加密保存的,所有传请求中要带有加密的key,密钥一定要十六位

  • key : Ek1+Ep1==Ek2+Ep2
1
2
// 显示金额
authed.POST("money", api.ShowMoney)
1
2
3
4
5
6
7
8
9
10
func ShowMoney(c *gin.Context) {
var showMoneyService service.ShowMoneyService
claim, _ := utils.ParseToken(c.GetHeader("Authorization"))
if err := c.ShouldBind(&showMoneyService); err == nil {
res := showMoneyService.Show(c.Request.Context(), claim.ID)
c.JSON(200, res)
} else {
c.JSON(400, err)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type ShowMoneyService struct {
Key string `json:"key" form:"key"`
}

// Show 查看用户金额
func (service *ShowMoneyService) Show(ctx context.Context, uid uint) serializer.Response {
var code int = e.SUCCESS
userDao := dao.NewUserDao(ctx)
user, err := userDao.GetUserById(uid)
if err != nil {
return serializer.Result(e.ErrorDatabase)
}
return serializer.Response{
Status: code,
Data: serializer.BuildMoney(user, service.Key),
Msg: e.GetMsg(code),
}
}

serializer.BuildMoney:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package serializer


type Money struct {
UserID uint `json:"user_id" form:"user_id"`
UserName string `json:"user_name" form:"user_name"`
UserMoney string `json:"user_money" form:"user_money"`
}

func BuildMoney(item *model.User, key string) Money {
utils.Encrypt.SetKey(key)
return Money{
UserID: item.ID,
UserName: item.UserName,
UserMoney: utils.Encrypt.AesDecoding(item.Money),
}
}


二、轮播图

8、获取轮播图

1
v1.GET("carousels", api.ListCarousels) // 轮播图
1
2
3
4
5
6
7
8
9
func ListCarousels(c *gin.Context) {
var listCarouselService service.ListCarouselService
if err := c.ShouldBind(&listCarouselService); err == nil {
res := listCarouselService.Show(c)
c.JSON(200, res)
} else {
c.JSON(400, err)
}
}
1
2
3
4
5
6
7
8
9
10
11
type ListCarouselService struct {
}

func (service *ListCarouselService) Show(ctx context.Context) serializer.Response {
cauouselsdao := dao.NewCarouselDao(ctx)
carousels, err := cauouselsdao.List()
if err != nil {
return serializer.Result(e.ErrorDatabase)
}
return serializer.BuildListResponse(serializer.BuildCarousels(carousels), uint(len(carousels)))
}

请求成功返回序列化后的结果:

serializer.BuildCarousels:

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
package serializer

import "example.com/unicorn-acc/model"

type Carousel struct {
ID uint `json:"id"`
ImgPath string `json:"img_path"`
ProductID uint `json:"product_id"`
CreatedAt int64 `json:"created_at"`
}

// BuildCarousel 序列化轮播图
func BuildCarousel(item *model.Carousel) Carousel {
return Carousel{
ID: item.ID,
ImgPath: item.ImgPath,
ProductID: item.ProductID,
CreatedAt: item.CreatedAt.Unix(),
}
}

// BuildCarousels 序列化轮播图列表
func BuildCarousels(items []*model.Carousel) (carousels []Carousel) {
for _, item := range items {
carousel := BuildCarousel(item)
carousels = append(carousels, carousel)
}
return carousels
}

BuildListResponse

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// DataList 带有总数的Data结构
type DataList struct {
Item interface{} `json:"item"`
Total uint `json:"total"`
}

func BuildListResponse(items interface{}, total uint) Response {
return Response{
Status: 200,
Data: DataList{
Item: items,
Total: total,
},
Msg: "ok",
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
type CarouselDao struct {
*gorm.DB
}

func NewCarouselDao(ctx context.Context) *CarouselDao {
return &CarouselDao{NewDBClient(ctx)}
}

func (dao *CarouselDao) List() (carousels []*model.Carousel, err error) {
err = dao.DB.Model(&model.Carousel{}).Find(&carousels).Error
return
}
s


三、商品模块

✨9、创建商品

1
2
// 商品操作
authed.POST("product", api.CreateProduct)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 创建商品
func CreateProduct(c *gin.Context) {
form, _ := c.MultipartForm()
files := form.File["file"]
claim, _ := utils.ParseToken(c.GetHeader("Authorization"))
var createProductService service.ProductService
//c.SaveUploadedFile()
if err := c.ShouldBind(&createProductService); err == nil {
res := createProductService.Create(c.Request.Context(), claim.ID, files)
c.JSON(200, res)
} else {
c.JSON(400, ErrorResponse(err))
utils.LogrusObj.Infoln(err)
}
}
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
// ProductService 商品创建、更新、列表 使用同一个服务
type ProductService struct {
ID uint `form:"id" json:"id"`
Name string `form:"name" json:"name"`
CategoryID int `form:"category_id" json:"category_id"`
Title string `form:"title" json:"title" `
Info string `form:"info" json:"info" `
ImgPath string `form:"img_path" json:"img_path"`
Price string `form:"price" json:"price"`
DiscountPrice string `form:"discount_price" json:"discount_price"`
OnSale bool `form:"on_sale" json:"on_sale"`
Num int `form:"num" json:"num"`
model.BasePage // 这个是model上的分页功能
}

func (service *ProductService) Create(ctx context.Context, uId uint, files []*multipart.FileHeader) serializer.Response {
var boss *model.User
var err error
code := e.SUCCESS
// 1. 获取当前创建商品的用户
userDao := dao.NewUserDao(ctx)
boss, _ = userDao.GetUserById(uId)
// 2. 以第一张作为封面图,获得图片的路径
tmp, _ := files[0].Open()
path, err := UploadProductToLocalStatic(tmp, uId, service.Name)
if err != nil {
code = e.ErrorUploadFile
return serializer.Response{
Status: code,
Data: e.GetMsg(code),
Error: path,
}
}
// 3. 将商品添加进数据库
product := &model.Product{
Name: service.Name,
CategoryID: uint(service.CategoryID),
Title: service.Title,
Info: service.Info,
ImgPath: path,
Price: service.Price,
DiscountPrice: service.DiscountPrice,
Num: service.Num,
OnSale: true,
BossID: uId,
BossName: boss.UserName,
BossAvatar: boss.Avatar,
}
productDao := dao.NewProductDao(ctx)
err = productDao.CreateProduct(product)
if err != nil {
logging.Info(err)
code = e.ErrorDatabase
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
Error: err.Error(),
}
}
// 4. todo 并发保存商品的图片
wg := new(sync.WaitGroup)
wg.Add(len(files))
for index, file := range files {
num := strconv.Itoa(index)
productImgDao := dao.NewProductImgDaoByDB(productDao.DB)
// 4.1 上传商品图片,获得路径
tmp, _ = file.Open()
path, err = UploadProductToLocalStatic(tmp, uId, service.Name+num)
if err != nil {
code = e.ErrorUploadFile
return serializer.Response{
Status: code,
Data: e.GetMsg(code),
Error: path,
}
}
// 4.2 上传商品图片
productImg := &model.ProductImg{
ProductID: product.ID,
ImgPath: path,
}
err = productImgDao.CreateProductImg(productImg)
if err != nil {
code = e.ERROR
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
Error: err.Error(),
}
}
wg.Done()
}
wg.Wait()
return serializer.Response{
Status: code,
Data: serializer.BuildProduct(product),
Msg: e.GetMsg(code),
}
}

保存商品信息的DAO

1
2
3
4
5
6
7
8
9
10
11
12
type ProductDao struct {
*gorm.DB
}

func NewProductDao(ctx context.Context) *ProductDao {
return &ProductDao{NewDBClient(ctx)}
}

// CreateProduct 创建商品
func (dao *ProductDao) CreateProduct(product *model.Product) error {
return dao.DB.Model(&model.Product{}).Create(&product).Error
}

保存商品信息图片的DAO

1
2
3
4
5
6
7
8
9
10
11
12
13
func NewProductImgDao(ctx context.Context) *ProductImgDao {
return &ProductImgDao{NewDBClient(ctx)}
}

func NewProductImgDaoByDB(db *gorm.DB) *ProductImgDao {
return &ProductImgDao{db}
}

// CreateProductImg 创建商品图片
func (dao *ProductImgDao) CreateProductImg(productImg *model.ProductImg) (err error) {
err = dao.DB.Model(&model.ProductImg{}).Create(&productImg).Error
return
}

并发保存商品图片

  • 使用了sync.WaitGroup
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
// 4. todo 并发保存商品的图片
wg := new(sync.WaitGroup)
wg.Add(len(files))
for index, file := range files {
num := strconv.Itoa(index)
productImgDao := dao.NewProductImgDaoByDB(productDao.DB)
// 4.1 上传商品图片,获得路径
tmp, _ = file.Open()
path, err = UploadProductToLocalStatic(tmp, uId, service.Name+num)
if err != nil {
code = e.ErrorUploadFile
return serializer.Response{
Status: code,
Data: e.GetMsg(code),
Error: path,
}
}
// 4.2 上传商品图片
productImg := &model.ProductImg{
ProductID: product.ID,
ImgPath: path,
}
err = productImgDao.CreateProductImg(productImg)
if err != nil {
code = e.ERROR
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
Error: err.Error(),
}
}
wg.Done()
}
wg.Wait()
return serializer.Response{
Status: code,
Data: serializer.BuildProduct(product),
Msg: e.GetMsg(code),
}

引入Redis保存点击数

①Redis配置

②编写key

③使用redis

model.product.go中创建两个方法(获取点击数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// View 获取点击数
func (product *Product) View() uint64 {
// 引入Redis 才可以使用 cache.ProductViewKey(product.ID)
countStr, _ := cache.RedisClient.Get(cache.ProductViewKey(product.ID)).Result()
count, _ := strconv.ParseUint(countStr, 10, 64)
return count
}

// AddView 商品游览
func (product *Product) AddView() {
// 增加视频点击数
cache.RedisClient.Incr(cache.ProductViewKey(product.ID))
// 增加排行点击数
cache.RedisClient.ZIncrBy(cache.RankKey, 1, strconv.Itoa(int(product.ID)))
}

10、获取商品列表

1
v1.GET("products", api.ListProducts)
1
2
3
4
5
6
7
8
9
10
func ListProducts(c *gin.Context) {
var listPorductService service.ProductService
if err := c.ShouldBind(&listPorductService); err == nil {
res := listPorductService.List(c.Request.Context())
c.JSON(200, res)
} else {
c.JSON(400, ErrorResponse(err))
utils.LogrusObj.Infoln(err)
}
}
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
func (service *ProductService) List(ctx context.Context) serializer.Response {
var products []*model.Product
var total int64
code := e.SUCCESS
// 1. 配置查询参数
if service.PageSize == 0 {
service.PageSize = 15
}
// condition : 查询的条件
condition := make(map[string]interface{})
if service.CategoryID != 0 {
condition["category_id"] = service.CategoryID // 某类商品还是全部商品
}
// 3.根据查询条件查询商品数量
productDao := dao.NewProductDao(ctx)
total, err := productDao.CountProductByCondition(condition)
if err != nil {
code = e.ErrorDatabase
utils.LogrusObj.Infoln(err)
return serializer.Result(code)
}
// 4. 并发获取
wg := new(sync.WaitGroup)
wg.Add(1)
go func() {
productDao = dao.NewProductDaoByDB(productDao.DB)
products, _ = productDao.ListProductByCondition(condition, service.BasePage)
wg.Done()
}()
wg.Wait()

// 5. 进行序列化并返回
return serializer.BuildListResponse(serializer.BuildProducts(products), uint(total))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 根据查询条件获取商品数量
func (dao *ProductDao) CountProductByCondition(condition map[string]interface{}) (total int64, err error) {
err = dao.DB.Model(&model.Product{}).Where(condition).Count(&total).Error
return
}
//-----------------------------------------------------------
func NewProductDaoByDB(db *gorm.DB) *ProductDao {
return &ProductDao{db}
}

func (dao *ProductDao) ListProductByCondition(condition map[string]interface{}, page model.BasePage) (products []*model.Product, err error) {
err = dao.DB.Model(&model.Product{}).Where(condition).
Offset((page.PageNum - 1) * page.PageSize). // 第pagenum页的数据
Limit(page.PageSize).Find(&products).Error
return
}

11、搜索商品

1
v1.POST("products", api.SearchProducts)
1
2
3
4
5
6
7
8
9
10
11
func SearchProducts(c *gin.Context) {
var searchPorductService service.ProductService
if err := c.ShouldBind(&searchPorductService); err == nil {
res := searchPorductService.Search(c.Request.Context())
c.JSON(200, res)
} else {
c.JSON(400, ErrorResponse(err))
utils.LogrusObj.Infoln(err)
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func (service *ProductService) Search(ctx context.Context) serializer.Response {
var code int = e.SUCCESS
if service.PageSize == 0 {
service.PageSize = 15
}

productDao := dao.NewProductDao(ctx)
products, err := productDao.SearchProduct(service.Info, service.BasePage)
if err != nil {
logging.Info(err)
code = e.ErrorDatabase
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
}
}
return serializer.BuildListResponse(serializer.BuildProducts(products), uint(len(products)))
}
1
2
3
4
5
6
7
8
// SearchProduct 搜索商品
func (dao *ProductDao) SearchProduct(info string, page model.BasePage) (products []*model.Product, err error) {
err = dao.DB.Model(&model.Product{}).
Where("name LIKE ? OR info LIKE ?", "%"+info+"%", "%"+info+"%").
Offset((page.PageNum - 1) * page.PageSize).
Limit(page.PageSize).Find(&products).Error
return
}


12、商品展示信息

1
v1.GET("product/:id", api.ShowProduct)
1
2
3
4
5
func ShowProduct(c *gin.Context) {
var showProductService service.ProductService
res := showProductService.Show(c.Request.Context(), c.Param("id"))
c.JSON(http.StatusOK, res)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func (service *ProductService) Show(ctx context.Context, id string) serializer.Response {
code := e.SUCCESS
pId, _ := strconv.Atoi(id)
productDao := dao.NewProductDao(ctx)
product, err := productDao.GetProductById(uint(pId))
if err != nil {
logging.Info(err)
code = e.ErrorDatabase
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
Error: err.Error(),
}
}
return serializer.Response{
Status: code,
Data: serializer.BuildProduct(product),
Msg: e.GetMsg(code),
}
}
1
2
3
4
func (dao *ProductDao) GetProductById(uid uint) (product *model.Product, err error) {
err = dao.DB.Model(&model.Product{}).Where("id = ?", uid).First(&product).Error
return
}

13、获取图片地址

1
v1.GET("imgs/:id", api.ListProductImg) // 商品图片
1
2
3
4
5
6
7
8
9
10
func ListProductImg(c *gin.Context) {
var listPorductImgService service.ListProductImgService
if err := c.ShouldBind(&listPorductImgService); err == nil {
res := listPorductImgService.List(c.Request.Context(), c.Param("id"))
c.JSON(200, res)
} else {
c.JSON(400, ErrorResponse(err))
utils.LogrusObj.Infoln(err)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type ListProductImgService struct {
}

func (service *ListProductImgService) List(ctx context.Context, id string) serializer.Response {
var productImg []*model.ProductImg
code := e.SUCCESS
pId, _ := strconv.Atoi(id)
productImgDao := dao.NewProductImgDao(ctx)
productImg, err := productImgDao.GetproductImgById(uint(pId))
if err != nil {
code = e.ErrorDatabase
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
}
}
return serializer.BuildListResponse(serializer.BuildProductImgs(productImg), uint(len(productImg)))
}

返回结果的序列化器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type ProductImg struct {
ProductID uint `json:"product_id" form:"product_id"`
ImgPath string `json:"img_path" form:"img_path"`
}

func BuildProductImg(item *model.ProductImg) ProductImg {
return ProductImg{
ProductID: item.ProductID,
ImgPath: conf.PhotoHost + conf.HttpPort + conf.ProductPhotoPath + item.ImgPath,
}
}

func BuildProductImgs(items []*model.ProductImg) (productImgs []ProductImg) {
for _, item := range items {
productimg := BuildProductImg(item)
productImgs = append(productImgs, productimg)
}
return
}
1
2
3
4
func (dao *ProductImgDao) GetproductImgById(id uint) (products []*model.ProductImg, err error) {
err = dao.DB.Model(&model.ProductImg{}).Where("product_id = ?", id).Find(&products).Error
return
}

14、获取商品分类

1
v1.GET("categories", api.ListCategories) // 商品分类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type ListCategoriesService struct {
}

func (service *ListCategoriesService) List(ctx context.Context) serializer.Response {
code := e.SUCCESS
categoryDao := dao.NewCategoryDao(ctx)
categories, err := categoryDao.ListCategory()
if err != nil {
logging.Info(err)
code = e.ErrorDatabase
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
Error: err.Error(),
}
}
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
Data: serializer.BuildCategories(categories),
}
}

目录列表json序列器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//vo
type Category struct {
ID uint `json:"id"`
CategoryName string `json:"category_name"`
CreateAt int64 `json:"create_at"`
}

func BuildCategory(item *model.Category) Category {
return Category{
ID: item.ID,
CategoryName: item.CategoryName,
CreateAt: item.CreatedAt.Unix(),
}
}

func BuildCategories(items []*model.Category) (categories []Category) {
for _, item := range items {
category := BuildCategory(item)
categories = append(categories, category)
}
return categories
}
1
2
3
4
5
6
7
8
9
10
11
12
13
type CategoryDao struct {
*gorm.DB
}

func NewCategoryDao(ctx context.Context) *CategoryDao {
return &CategoryDao{NewDBClient(ctx)}
}

// ListCategory 分类列表
func (dao *CategoryDao) ListCategory() (category []*model.Category, err error) {
err = dao.DB.Model(&model.Category{}).Find(&category).Error
return
}


四、收藏夹操作(将收藏夹与用户,做的不太好)

15、创建收藏夹

1
authed.POST("favorites", api.CreateFavorite)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
func CreateProduct(c *gin.Context) {
form, _ := c.MultipartForm()
files := form.File["file"]
claim, _ := utils.ParseToken(c.GetHeader("Authorization"))
var createProductService service.ProductService
//c.SaveUploadedFile()
if err := c.ShouldBind(&createProductService); err == nil {
res := createProductService.Create(c.Request.Context(), claim.ID, files)
c.JSON(200, res)
} else {
c.JSON(400, ErrorResponse(err))
utils.LogrusObj.Infoln(err)
}
}
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
func (service *FavoritesService) Create(ctx context.Context, uid uint) serializer.Response {
code := e.SUCCESS
favoriteDao := dao.NewFavoritesDao(ctx)
// 1. 查看一下收藏夹是否存在该商品
exist, _ := favoriteDao.FavoriteExistOrNot(service.ProductId)
if exist { // 存在就不用存了
code = e.ErrorExistFavorite
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
}
}
// 2. 根据用户id获取一下用户信息
userDao := dao.NewUserDao(ctx)
user, err := userDao.GetUserById(uid)
if err != nil {
code = e.ErrorDatabase
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
}
}

// 3. 根据商品的归属id,获取一下boss信息
bossDao := dao.NewUserDaoByDB(userDao.DB)
boss, err := bossDao.GetUserById(service.BossId)
if err != nil {
code = e.ErrorDatabase
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
}
}

// 4. 获取一下商品信息
productDao := dao.NewProductDao(ctx)
product, err := productDao.GetProductById(service.ProductId)
if err != nil {
code = e.ErrorDatabase
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
}
}

favorite := &model.Favorite{
UserID: uid,
User: *user, // 存在外键约束
ProductID: service.ProductId,
Product: *product,
BossID: service.BossId,
Boss: *boss,
}
favoriteDao = dao.NewFavoritesDaoByDB(favoriteDao.DB)
err = favoriteDao.CreateFavorite(favorite)
if err != nil {
code = e.ErrorDatabase
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
}
}

return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
}

}

16、展示收藏夹

1
authed.GET("favorites", api.ShowFavorites)
1
2
3
4
5
6
7
8
9
10
11
12
// 收藏夹详情接口
func ShowFavorites(c *gin.Context) {
service := service.FavoritesService{}
claim, _ := utils.ParseToken(c.GetHeader("Authorization"))
if err := c.ShouldBind(&service); err == nil {
res := service.Show(c.Request.Context(), claim.ID)
c.JSON(200, res)
} else {
c.JSON(400, ErrorResponse(err))
utils.LogrusObj.Infoln(err)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Show 商品收藏夹
func (service *FavoritesService) Show(ctx context.Context, uId uint) serializer.Response {
favoritesDao := dao.NewFavoritesDao(ctx)
code := e.SUCCESS
if service.PageSize == 0 {
service.PageSize = 15
}
favorites, total, err := favoritesDao.ListFavoriteByUserId(uId, service.PageSize, service.PageNum)
if err != nil {
logging.Info(err)
code = e.ErrorDatabase
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
Error: err.Error(),
}
}
return serializer.BuildListResponse(serializer.BuildFavorites(ctx, favorites), uint(total))
}

序列化vo对象

  • 在这里根据bossid和商品ID查询信息不太好,应该在favoriteservice中调用userserice和productservice对象然后让他们查询返回id对应的信息
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
type Favorite struct {
UserID uint `json:"user_id"`
ProductID uint `json:"product_id"`
CreatedAt int64 `json:"create_at"`
Name string `json:"name"`
CategoryID uint `json:"category_id"`
Title string `json:"title"`
Info string `json:"info"`
ImgPath string `json:"img_path"`
Price string `json:"price"`
DiscountPrice string `json:"discount_price"`
BossID uint `json:"boss_id"`
Num int `json:"num"`
OnSale bool `json:"on_sale"`
}

//序列化收藏夹
func BuildFavorite(item1 *model.Favorite, item2 *model.Product, item3 *model.User) Favorite {
return Favorite{
UserID: item1.UserID,
ProductID: item1.ProductID,
CreatedAt: item1.CreatedAt.Unix(),
Name: item2.Name,
CategoryID: item2.CategoryID,
Title: item2.Title,
Info: item2.Info,
ImgPath: item2.ImgPath,
Price: item2.Price,
DiscountPrice: item2.DiscountPrice,
BossID: item3.ID,
Num: item2.Num,
OnSale: item2.OnSale,
}
}

// 收藏夹列表
func BuildFavorites(ctx context.Context, items []*model.Favorite) (favorites []Favorite) {
productDao := dao.NewProductDao(ctx)
bossDao := dao.NewUserDao(ctx)

for _, fav := range items {
product, err := productDao.GetProductById(fav.ProductID)
if err != nil {
continue
}
boss, err := bossDao.GetUserById(fav.UserID)
if err != nil {
continue
}
favorite := BuildFavorite(fav, product, boss)
favorites = append(favorites, favorite)
}
return favorites
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ListFavoriteByUserId 通过 user_id 获取收藏夹列表
func (dao *FavoritesDao) ListFavoriteByUserId(uId uint, pageSize, pageNum int) (favorites []*model.Favorite, total int64, err error) {
// 总数
err = dao.DB.Model(&model.Favorite{}).Preload("User").
Where("user_id=?", uId).Count(&total).Error
if err != nil {
return
}
// 分页
err = dao.DB.Model(model.Favorite{}).Preload("User").Where("user_id=?", uId).
Offset((pageNum - 1) * pageSize).
Limit(pageSize).Find(&favorites).Error
return
}

17、删除收藏夹

1
authed.DELETE("favorites/:id", api.DeleteFavorite)
1
2
3
4
5
6
7
8
9
10
func DeleteFavorite(c *gin.Context) {
service := service.FavoritesService{}
if err := c.ShouldBind(&service); err == nil {
res := service.Delete(c.Request.Context())
c.JSON(200, res)
} else {
c.JSON(400, ErrorResponse(err))
utils.LogrusObj.Infoln(err)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Delete 删除收藏夹
func (service *FavoritesService) Delete(ctx context.Context) serializer.Response {
code := e.SUCCESS

favoriteDao := dao.NewFavoritesDao(ctx)
err := favoriteDao.DeleteFavoriteById(service.FavoriteId)
if err != nil {
logging.Info(err)
code = e.ErrorDatabase
return serializer.Response{
Status: code,
Data: e.GetMsg(code),
Error: err.Error(),
}
}

return serializer.Response{
Status: code,
Data: e.GetMsg(code),
}
}
1
2
3
4
// DeleteFavoriteById 删除收藏夹
func (dao *FavoritesDao) DeleteFavoriteById(fId uint) error {
return dao.DB.Where("id=?", fId).Delete(&model.Favorite{}).Error
}

五、地址模块(简单CRUD)

路由:

1
2
3
4
5
6
// 收获地址操作
authed.POST("addresses", api.CreateAddress)
authed.GET("addresses/:id", api.GetAddress)
authed.GET("addresses", api.ListAddress)
authed.PUT("addresses/:id", api.UpdateAddress)
authed.DELETE("addresses/:id", api.DeleteAddress)

Controller层:

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
// CreateAddress 新增收货地址
func CreateAddress(c *gin.Context) {
addressService := service.AddressService{}
claim, _ := utils.ParseToken(c.GetHeader("Authorization"))
if err := c.ShouldBind(&addressService); err == nil {
res := addressService.Create(c.Request.Context(), claim.ID)
c.JSON(200, res)
} else {
c.JSON(400, ErrorResponse(err))
utils.LogrusObj.Infoln(err)
}
}

// GetAddress 展示某个收货地址
func GetAddress(c *gin.Context) {
addressService := service.AddressService{}
res := addressService.Show(c.Request.Context(), c.Param("id"))
c.JSON(200, res)
}

// ListAddress 展示收货地址
func ListAddress(c *gin.Context) {
addressService := service.AddressService{}
claim, _ := utils.ParseToken(c.GetHeader("Authorization"))
if err := c.ShouldBind(&addressService); err == nil {
res := addressService.List(c.Request.Context(), claim.ID)
c.JSON(200, res)
} else {
c.JSON(400, ErrorResponse(err))
utils.LogrusObj.Infoln(err)
}
}

// UpdateAddress 修改收货地址
func UpdateAddress(c *gin.Context) {
addressService := service.AddressService{}
claim, _ := utils.ParseToken(c.GetHeader("Authorization"))
if err := c.ShouldBind(&addressService); err == nil {
res := addressService.Update(c.Request.Context(), claim.ID, c.Param("id"))
c.JSON(200, res)
} else {
c.JSON(400, ErrorResponse(err))
utils.LogrusObj.Infoln(err)
}
}

// DeleteAddress 删除收获地址
func DeleteAddress(c *gin.Context) {
addressService := service.AddressService{}
if err := c.ShouldBind(&addressService); err == nil {
res := addressService.Delete(c.Request.Context(), c.Param("id"))
c.JSON(200, res)
} else {
c.JSON(400, ErrorResponse(err))
utils.LogrusObj.Infoln(err)
}
}

Service层:

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
package service

import (
"example.com/unicorn-acc/dao"
"example.com/unicorn-acc/model"
"example.com/unicorn-acc/pkg/e"
"example.com/unicorn-acc/serializer"
logging "github.com/sirupsen/logrus"
"golang.org/x/net/context"
"strconv"
)

type AddressService struct {
Name string `form:"name" json:"name"`
Phone string `form:"phone" json:"phone"`
Address string `form:"address" json:"address"`
}



func (service *AddressService) Create(ctx context.Context, uId uint) serializer.Response {
code := e.SUCCESS
addressDao := dao.NewAddressDao(ctx)
address := &model.Address{
UserID: uId,
Name: service.Name,
Phone: service.Phone,
Address: service.Address,
}
err := addressDao.CreateAddress(address)
if err != nil {
logging.Info(err)
code = e.ErrorDatabase
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
Error: err.Error(),
}
}
addressDao = dao.NewAddressDaoByDB(addressDao.DB)
var addresses []*model.Address
addresses, err = addressDao.ListAddressByUid(uId)
if err != nil {
logging.Info(err)
code = e.ErrorDatabase
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
Error: err.Error(),
}
}
return serializer.Response{
Status: code,
Data: serializer.BuildAddresses(addresses),
Msg: e.GetMsg(code),
}
}

func (service *AddressService) Show(ctx context.Context, aId string) serializer.Response {
code := e.SUCCESS
addressDao := dao.NewAddressDao(ctx)
addressId, _ := strconv.Atoi(aId)
address, err := addressDao.GetAddressByAid(uint(addressId))
if err != nil {
logging.Info(err)
code = e.ErrorDatabase
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
Error: err.Error(),
}
}
return serializer.Response{
Status: code,
Data: serializer.BuildAddress(address),
Msg: e.GetMsg(code),
}
}

func (service *AddressService) List(ctx context.Context, uId uint) serializer.Response {
code := e.SUCCESS
addressDao := dao.NewAddressDao(ctx)
address, err := addressDao.ListAddressByUid(uId)
if err != nil {
logging.Info(err)
code = e.ErrorDatabase
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
Error: err.Error(),
}
}
return serializer.Response{
Status: code,
Data: serializer.BuildAddresses(address),
Msg: e.GetMsg(code),
}
}

func (service *AddressService) Delete(ctx context.Context, aId string) serializer.Response {
addressDao := dao.NewAddressDao(ctx)
code := e.SUCCESS
addressId, _ := strconv.Atoi(aId)
err := addressDao.DeleteAddressById(uint(addressId))
if err != nil {
logging.Info(err)
code = e.ErrorDatabase
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
Error: err.Error(),
}
}
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
}
}

func (service *AddressService) Update(ctx context.Context, uid uint, aid string) serializer.Response {
code := e.SUCCESS

addressDao := dao.NewAddressDao(ctx)
address := &model.Address{
UserID: uid,
Name: service.Name,
Phone: service.Phone,
Address: service.Address,
}
addressId, _ := strconv.Atoi(aid)
err := addressDao.UpdateAddressById(uint(addressId), address)
addressDao = dao.NewAddressDaoByDB(addressDao.DB)
var addresses []*model.Address
addresses, err = addressDao.ListAddressByUid(uid)
if err != nil {
logging.Info(err)
code = e.ErrorDatabase
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
Error: err.Error(),
}
}
return serializer.Response{
Status: code,
Data: serializer.BuildAddresses(addresses),
Msg: e.GetMsg(code),
}
}

序列化vo:

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
type Address struct {
ID uint `json:"id"`
UserID uint `json:"user_id"`
Name string `json:"name"`
Phone string `json:"phone"`
Address string `json:"address"`
Seen bool `json:"seen"`
CreateAt int64 `json:"create_at"`
}

//收货地址购物车
func BuildAddress(item *model.Address) Address {
return Address{
ID: item.ID,
UserID: item.UserID,
Name: item.Name,
Phone: item.Phone,
Address: item.Address,
Seen: false,
CreateAt: item.CreatedAt.Unix(),
}
}

//收货地址列表
func BuildAddresses(items []*model.Address) (addresses []Address) {
for _, item := range items {
address := BuildAddress(item)
addresses = append(addresses, address)
}
return addresses
}

DAO层:

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

type AddressDao struct {
*gorm.DB
}

func NewAddressDao(ctx context.Context) *AddressDao {
return &AddressDao{NewDBClient(ctx)}
}

func NewAddressDaoByDB(db *gorm.DB) *AddressDao {
return &AddressDao{db}
}

// GetAddressByAid 根据 Address Id 获取 Address
func (dao *AddressDao) GetAddressByAid(aId uint) (address *model.Address, err error) {
err = dao.DB.Model(&model.Address{}).
Where("id = ?", aId).First(&address).
Error
return
}

// ListAddressByUid 根据 User Id 获取User
func (dao *AddressDao) ListAddressByUid(uid uint) (addressList []*model.Address, err error) {
err = dao.DB.Model(&model.Address{}).
Where("user_id=?", uid).Order("created_at desc").
Find(&addressList).Error
return
}

// CreateAddress 创建地址
func (dao *AddressDao) CreateAddress(address *model.Address) (err error) {
err = dao.DB.Model(&model.Address{}).Create(&address).Error
return
}

// DeleteAddressById 根据 id 删除地址
func (dao *AddressDao) DeleteAddressById(aId uint) (err error) {
err = dao.DB.Where("id=?", aId).Delete(&model.Address{}).Error
return
}

// UpdateAddressById 通过 id 修改地址信息
func (dao *AddressDao) UpdateAddressById(aId uint, address *model.Address) (err error) {
err = dao.DB.Model(&model.Address{}).
Where("id=?", aId).Updates(address).Error
return
}

六、购物车操作(简单CRUD)

18、创建购物车

1
2
// 购物车
authed.POST("carts", api.CreateCart)
1
2
3
4
5
6
7
8
9
10
11
func CreateCart(c *gin.Context) {
createCartService := service.CartService{}
claim, _ := utils.ParseToken(c.GetHeader("Authorization"))
if err := c.ShouldBind(&createCartService); err == nil {
res := createCartService.Create(c.Request.Context(), claim.ID)
c.JSON(200, res)
} else {
c.JSON(400, ErrorResponse(err))
utils.LogrusObj.Infoln(err)
}
}
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
package service

// CartService 创建购物车
type CartService struct {
Id uint `form:"id" json:"id"`
BossID uint `form:"boss_id" json:"boss_id"`
ProductId uint `form:"product_id" json:"product_id"`
Num uint `form:"num" json:"num"`
}

func (service *CartService) Create(ctx context.Context, uId uint) serializer.Response {
var product *model.Product
code := e.SUCCESS

// 判断有无这个商品
productDao := dao.NewProductDao(ctx)
product, err := productDao.GetProductById(service.ProductId)
if err != nil {
logging.Info(err)
code = e.ErrorDatabase
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
Error: err.Error(),
}
}

// 创建购物车
cartDao := dao.NewCartDao(ctx)
cart, status, err := cartDao.CreateCart(service.ProductId, uId, service.BossID)
if status == e.ErrorProductMoreCart {
return serializer.Response{
Status: status,
Msg: e.GetMsg(status),
}
}

userDao := dao.NewUserDao(ctx)
boss, err := userDao.GetUserById(service.BossID)
return serializer.Response{
Status: status,
Msg: e.GetMsg(status),
Data: serializer.BuildCart(cart, product, boss),
}
}

购物车的vo

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
// 购物车
type Cart struct {
ID uint `json:"id"`
UserID uint `json:"user_id"`
ProductID uint `json:"product_id"`
CreateAt int64 `json:"create_at"`
Num uint `json:"num"`
MaxNum uint `json:"max_num"`
Check bool `json:"check"`
Name string `json:"name"`
ImgPath string `json:"img_path"`
DiscountPrice string `json:"discount_price"`
BossId uint `json:"boss_id"`
BossName string `json:"boss_name"`
Desc string `json:"desc"`
}

func BuildCart(cart *model.Cart, product *model.Product, boss *model.User) Cart {
return Cart{
ID: cart.ID,
UserID: cart.UserID,
ProductID: cart.ProductID,
CreateAt: cart.CreatedAt.Unix(),
Num: cart.Num,
MaxNum: cart.MaxNum,
Check: cart.Check,
Name: product.Name,
ImgPath: conf.PhotoHost + conf.HttpPort + conf.ProductPhotoPath + product.ImgPath,
DiscountPrice: product.DiscountPrice,
BossId: boss.ID,
BossName: boss.UserName,
Desc: product.Info,
}
}

func BuildCarts(items []*model.Cart) (carts []Cart) {
for _, item1 := range items {
product, err := dao.NewProductDao(context.Background()).
GetProductById(item1.ProductID)
if err != nil {
continue
}
boss, err := dao.NewUserDao(context.Background()).
GetUserById(item1.BossID)
if err != nil {
continue
}
cart := BuildCart(item1, product, boss)
carts = append(carts, cart)
}
return carts
}

DAO

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
package dao

type CartDao struct {
*gorm.DB
}

func NewCartDao(ctx context.Context) *CartDao {
return &CartDao{NewDBClient(ctx)}
}

func NewCartDaoByDB(db *gorm.DB) *CartDao {
return &CartDao{db}
}

// CreateCart 创建 cart pId(商品 id)、uId(用户id)、bId(店家id)
func (dao *CartDao) CreateCart(pId, uId, bId uint) (cart *model.Cart, status int, err error) {
// 查询有无此条商品
cart, err = dao.GetCartById(pId, uId, bId)
// 空的,第一次加入
if err == gorm.ErrRecordNotFound {
cart = &model.Cart{
UserID: uId,
ProductID: pId,
BossID: bId,
Num: 1,
MaxNum: 10,
Check: false,
}
err = dao.DB.Create(&cart).Error
if err != nil {
return
}
return cart, e.SUCCESS, err
} else if cart.Num < cart.MaxNum {
// 小于最大 num
cart.Num++
err = dao.DB.Save(&cart).Error
if err != nil {
return
}
return cart, e.ErrorProductExistCart, err
} else {
// 大于最大num
return cart, e.ErrorProductMoreCart, err
}
}

// GetCartById 获取 Cart 通过 Id
func (dao *CartDao) GetCartById(pId, uId, bId uint) (cart *model.Cart, err error) {
err = dao.DB.Model(&model.Cart{}).
Where("user_id=? AND product_id=? AND boss_id=?", uId, pId, bId).
First(&cart).Error
return
}

19、查看、更新、删除购物车

1
2
3
authed.GET("carts", api.ShowCarts)
authed.PUT("carts/:id", api.UpdateCart) // 购物车id
authed.DELETE("carts/:id", api.DeleteCart)
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
// 购物车详细信息
func ShowCarts(c *gin.Context) {
showCartsService := service.CartService{}
claim, _ := utils.ParseToken(c.GetHeader("Authorization"))
res := showCartsService.Show(c.Request.Context(), claim.ID)
c.JSON(200, res)
}

// 修改购物车信息
func UpdateCart(c *gin.Context) {
updateCartService := service.CartService{}
if err := c.ShouldBind(&updateCartService); err == nil {
res := updateCartService.Update(c.Request.Context(), c.Param("id"))
c.JSON(200, res)
} else {
c.JSON(400, ErrorResponse(err))
utils.LogrusObj.Infoln(err)
}
}

// 删除购物车
func DeleteCart(c *gin.Context) {
deleteCartService := service.CartService{}
if err := c.ShouldBind(&deleteCartService); err == nil {
res := deleteCartService.Delete(c.Request.Context())
c.JSON(200, res)
} else {
c.JSON(400, ErrorResponse(err))
utils.LogrusObj.Infoln(err)
}
}
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
// Show 购物车
func (service *CartService) Show(ctx context.Context, uId uint) serializer.Response {
code := e.SUCCESS
cartDao := dao.NewCartDao(ctx)
carts, err := cartDao.ListCartByUserId(uId)
if err != nil {
logging.Info(err)
code = e.ErrorDatabase
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
Error: err.Error(),
}
}
return serializer.Response{
Status: code,
Data: serializer.BuildCarts(carts),
Msg: e.GetMsg(code),
}
}

// Update 修改购物车信息
func (service *CartService) Update(ctx context.Context, cId string) serializer.Response {
code := e.SUCCESS
cartId, _ := strconv.Atoi(cId)

cartDao := dao.NewCartDao(ctx)
err := cartDao.UpdateCartNumById(uint(cartId), service.Num)
if err != nil {
logging.Info(err)
code = e.ErrorDatabase
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
Error: err.Error(),
}
}

return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
}
}

// 删除购物车
func (service *CartService) Delete(ctx context.Context) serializer.Response {
code := e.SUCCESS
cartDao := dao.NewCartDao(ctx)
err := cartDao.DeleteCartById(service.Id)
if err != nil {
logging.Info(err)
code = e.ErrorDatabase
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
Error: err.Error(),
}
}
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// ListCartByUserId 获取 Cart 通过 user_id
func (dao *CartDao) ListCartByUserId(uId uint) (cart []*model.Cart, err error) {
err = dao.DB.Model(&model.Cart{}).
Where("user_id=?", uId).Find(&cart).Error
return
}

// UpdateCartNumById 通过id更新Cart信息
func (dao *CartDao) UpdateCartNumById(cId, num uint) error {
return dao.DB.Model(&model.Cart{}).
Where("id=?", cId).Update("num", num).Error
}

// DeleteCartById 通过 cart_id 删除 cart
func (dao *CartDao) DeleteCartById(cId uint) error {
return dao.DB.Model(&model.Cart{}).
Where("id=?", cId).Delete(&model.Cart{}).Error
}

七、订单操作

20、创建、获取、获取详细信息、删除订单(简单CRUD)

1
2
3
4
5
// 订单操作
authed.POST("orders", api.CreateOrder)
authed.GET("orders", api.ListOrders)
authed.GET("orders/:id", api.ShowOrder)
authed.DELETE("orders/:id", api.DeleteOrder)
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
func CreateOrder(c *gin.Context) {
createOrderService := service.OrderService{}
claim, _ := utils.ParseToken(c.GetHeader("Authorization"))
if err := c.ShouldBind(&createOrderService); err == nil {
res := createOrderService.Create(c.Request.Context(), claim.ID)
c.JSON(200, res)
} else {
c.JSON(400, ErrorResponse(err))
utils.LogrusObj.Infoln(err)
}
}

func ListOrders(c *gin.Context) {
listOrdersService := service.OrderService{}
claim, _ := utils.ParseToken(c.GetHeader("Authorization"))
if err := c.ShouldBind(&listOrdersService); err == nil {
res := listOrdersService.List(c.Request.Context(), claim.ID)
c.JSON(200, res)
} else {
c.JSON(400, ErrorResponse(err))
utils.LogrusObj.Infoln(err)
}
}

// 订单详情
func ShowOrder(c *gin.Context) {
showOrderService := service.OrderService{}
if err := c.ShouldBind(&showOrderService); err == nil {
res := showOrderService.Show(c.Request.Context(), c.Param("id"))
c.JSON(200, res)
} else {
c.JSON(400, ErrorResponse(err))
utils.LogrusObj.Infoln(err)
}
}

func DeleteOrder(c *gin.Context) {
deleteOrderService := service.OrderService{}
if err := c.ShouldBind(&deleteOrderService); err == nil {
res := deleteOrderService.Delete(c.Request.Context(), c.Param("id"))
c.JSON(200, res)
} else {
c.JSON(400, ErrorResponse(err))
utils.LogrusObj.Infoln(err)
}
}
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
153
154
155
156
157
158
159
160
161
162
package service

// 存放在Redis中的Key
const OrderTimeKey = "OrderTime"

type OrderService struct {
ProductID uint `form:"product_id" json:"product_id"`
Num uint `form:"num" json:"num"`
AddressID uint `form:"address_id" json:"address_id"`
Money int `form:"money" json:"money"`
BossID uint `form:"boss_id" json:"boss_id"`
UserID uint `form:"user_id" json:"user_id"`
OrderNum uint `form:"order_num" json:"order_num"`
Type int `form:"type" json:"type"`
model.BasePage
}

func (service *OrderService) Create(ctx context.Context, id uint) serializer.Response {
code := e.SUCCESS

order := &model.Order{
UserID: id,
ProductID: service.ProductID,
BossID: service.BossID,
Num: int(service.Num),
Money: float64(service.Money),
Type: 1,
}
// 检验地址是否存在
addressDao := dao.NewAddressDao(ctx)
address, err := addressDao.GetAddressByAid(service.AddressID)
if err != nil {
logging.Info(err)
code = e.ErrorDatabase
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
Error: err.Error(),
}
}

order.AddressID = address.ID

// 设置订单号:自动生成的随机Number + 唯一标识的productid + 用户id
number := fmt.Sprintf("%09v", rand.New(rand.NewSource(time.Now().UnixNano())).Int31n(1000000000))
productNum := strconv.Itoa(int(service.ProductID))
userNum := strconv.Itoa(int(id))
number = number + productNum + userNum
orderNum, _ := strconv.ParseUint(number, 10, 64)
order.OrderNum = orderNum

orderDao := dao.NewOrderDao(ctx)
err = orderDao.CreateOrder(order)
if err != nil {
logging.Info(err)
code = e.ErrorDatabase
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
Error: err.Error(),
}
}

//订单号存入Redis中,设置过期时间
data := redis.Z{
Score: float64(time.Now().Unix()) + 15*time.Minute.Seconds(),
Member: orderNum,
}
cache.RedisClient.ZAdd(OrderTimeKey, data)
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
}
}

func (service *OrderService) List(ctx context.Context, uId uint) serializer.Response {
var orders []*model.Order
var total int64
code := e.SUCCESS
if service.PageSize == 0 {
service.PageSize = 5
}

orderDao := dao.NewOrderDao(ctx)
condition := make(map[string]interface{})
condition["user_id"] = uId

if service.Type == 0 {
condition["type"] = 0
} else {
condition["type"] = service.Type
}
orders, total, err := orderDao.ListOrderByCondition(condition, service.BasePage)
if err != nil {
code = e.ErrorDatabase
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
}
}

return serializer.BuildListResponse(serializer.BuildOrders(ctx, orders), uint(total))
}

func (service *OrderService) Show(ctx context.Context, uId string) serializer.Response {
code := e.SUCCESS

orderId, _ := strconv.Atoi(uId)
orderDao := dao.NewOrderDao(ctx)
order, _ := orderDao.GetOrderById(uint(orderId))

addressDao := dao.NewAddressDao(ctx)
address, err := addressDao.GetAddressByAid(order.AddressID)
if err != nil {
logging.Info(err)
code = e.ErrorDatabase
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
}
}

productDao := dao.NewProductDao(ctx)
product, err := productDao.GetProductById(order.ProductID)
if err != nil {
logging.Info(err)
code = e.ErrorDatabase
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
}
}

return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
Data: serializer.BuildOrder(order, product, address),
}
}

func (service *OrderService) Delete(ctx context.Context, oId string) serializer.Response {
code := e.SUCCESS

orderDao := dao.NewOrderDao(ctx)
orderId, _ := strconv.Atoi(oId)
err := orderDao.DeleteOrderById(uint(orderId))
if err != nil {
logging.Info(err)
code = e.ErrorDatabase
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
Error: err.Error(),
}
}

return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
}
}

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
package dao

type OrderDao struct {
*gorm.DB
}

func NewOrderDao(ctx context.Context) *OrderDao {
return &OrderDao{NewDBClient(ctx)}
}

func NewOrderDaoByDB(db *gorm.DB) *OrderDao {
return &OrderDao{db}
}

// CreateOrder 创建订单
func (dao *OrderDao) CreateOrder(order *model.Order) error {
return dao.DB.Create(&order).Error
}

// ListOrderByCondition 获取订单List
func (dao *OrderDao) ListOrderByCondition(condition map[string]interface{}, page model.BasePage) (orders []*model.Order, total int64, err error) {
err = dao.DB.Model(&model.Order{}).Where(condition).
Count(&total).Error
if err != nil {
return nil, 0, err
}

err = dao.DB.Model(&model.Order{}).Where(condition).
Offset((page.PageNum - 1) * page.PageSize).
Limit(page.PageSize).Order("created_at desc").Find(&orders).Error
return
}

// GetOrderById 获取订单详情
func (dao *OrderDao) GetOrderById(id uint) (order *model.Order, err error) {
err = dao.DB.Model(&model.Order{}).Where("id=?", id).
First(&order).Error
return
}

// DeleteOrderById 获取订单详情
func (dao *OrderDao) DeleteOrderById(id uint) error {
return dao.DB.Where("id=?", id).Delete(&model.Order{}).Error
}

// UpdateOrderById 更新订单详情
func (dao *OrderDao) UpdateOrderById(id uint, order *model.Order) error {
return dao.DB.Where("id=?", id).Updates(order).Error
}

序列化VO对象:

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 serializer

type Order struct {
ID uint `json:"id"`
OrderNum uint64 `json:"order_num"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
UserID uint `json:"user_id"`
ProductID uint `json:"product_id"`
BossID uint `json:"boss_id"`
Num uint `json:"num"`
AddressName string `json:"address_name"`
AddressPhone string `json:"address_phone"`
Address string `json:"address"`
Type uint `json:"type"`
Name string `json:"name"`
ImgPath string `json:"img_path"`
DiscountPrice string `json:"discount_price"`
}

func BuildOrder(item1 *model.Order, item2 *model.Product, item3 *model.Address) Order {
return Order{
ID: item1.ID,
OrderNum: item1.OrderNum,
CreatedAt: item1.CreatedAt.Unix(),
UpdatedAt: item1.UpdatedAt.Unix(),
UserID: item1.UserID,
ProductID: item1.ProductID,
BossID: item1.BossID,
Num: uint(item1.Num),
AddressName: item3.Name,
AddressPhone: item3.Phone,
Address: item3.Address,
Type: item1.Type,
Name: item2.Name,
ImgPath: conf.PhotoHost + conf.HttpPort + conf.ProductPhotoPath + item2.ImgPath,
DiscountPrice: item2.DiscountPrice,
}
}

func BuildOrders(ctx context.Context, items []*model.Order) (orders []Order) {
productDao := dao.NewProductDao(ctx)
addressDao := dao.NewAddressDao(ctx)

for _, item := range items {
product, err := productDao.GetProductById(item.ProductID)
if err != nil {
continue
}
address, err := addressDao.GetAddressByAid(item.AddressID)
if err != nil {
continue
}
order := BuildOrder(item, product, address)
orders = append(orders, order)
}
return orders
}


21、订单支付

1
2
// 支付功能
authed.POST("paydown", api.OrderPay)
1
2
3
4
5
6
7
8
9
10
11
func OrderPay(c *gin.Context) {
orderPayService := service.OrderPay{}
claim, _ := utils.ParseToken(c.GetHeader("Authorization"))
if err := c.ShouldBind(&orderPayService); err == nil {
res := orderPayService.PayDown(c.Request.Context(), claim.ID)
c.JSON(200, res)
} else {
utils.LogrusObj.Infoln(err)
c.JSON(400, ErrorResponse(err))
}
}
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
type OrderPay struct {
OrderId uint `form:"order_id" json:"order_id"`
Money float64 `form:"money" json:"money"`
OrderNo string `form:"orderNo" json:"orderNo"`
ProductID int `form:"product_id" json:"product_id"`
PayTime string `form:"payTime" json:"payTime" `
Sign string `form:"sign" json:"sign" `
BossID int `form:"boss_id" json:"boss_id"`
BossName string `form:"boss_name" json:"boss_name"`
Num int `form:"num" json:"num"`
Key string `form:"key" json:"key"`
}

func (service *OrderPay) PayDown(ctx context.Context, uid uint) serializer.Response {
code := e.SUCCESS

// 订单需要事务处理
err := dao.NewOrderDao(ctx).Transaction(func(tx *gorm.DB) error {
utils.Encrypt.SetKey(service.Key)
orderDao := dao.NewOrderDaoByDB(tx)
// 1. 根据订单ID获取订单信息,计算订单金额
order, err := orderDao.GetOrderById(service.OrderId)
if err != nil {
logging.Info(err)
return err
}
money := order.Money
num := order.Num
money = money * float64(num)

// 2. 查看下单用户存不存在
userDao := dao.NewUserDao(ctx)
user, err := userDao.GetUserById(uid)
if err != nil {
logging.Info(err)
code = e.ErrorDatabase
return err
}

// 3.对用户的钱进行解密,减去订单 再加密保存
moneyStr := utils.Encrypt.AesDecoding(user.Money)
moneyFloat, _ := strconv.ParseFloat(moneyStr, 64)
if moneyFloat-money < 0.0 { // 用户余额不足,进行回滚
logging.Info(err)
code = e.ErrorDatabase
return errors.New("金币不足")
}
// 4.扣除用户的钱
finMoney := fmt.Sprintf("%f", moneyFloat-money)
user.Money = utils.Encrypt.AesEncoding(finMoney)
err = userDao.UpdateUserById(uid, user)
if err != nil { // 更新用户金额失败
logging.Info(err)
code = e.ErrorDatabase
return err
}
// 5. 添加商品商家的金额
boss := new(model.User)
boss, err = userDao.GetUserById(uint(service.BossID))
moneyStr = utils.Encrypt.AesDecoding(boss.Money)
moneyFloat, _ = strconv.ParseFloat(moneyStr, 64)
finMoney = fmt.Sprintf("%f", moneyFloat+money)
boss.Money = utils.Encrypt.AesEncoding(finMoney)
err = userDao.UpdateUserById(uint(service.BossID), boss)
if err != nil { // 更新boss金额失败,回滚
logging.Info(err)
code = e.ErrorDatabase
return err
}
// 6. 更新产品数量
product := new(model.Product)
productDao := dao.NewProductDao(ctx)
product, err = productDao.GetProductById(uint(service.ProductID))
product.Num -= num
err = productDao.UpdateProduct(uint(service.ProductID), product)
if err != nil {
logging.Info(err)
code = e.ErrorDatabase
return err
}
// 7. 更新订单状态
order.Type = 2
err = orderDao.UpdateOrderById(service.OrderId, order)
if err != nil { // 更新订单失败,回滚
logging.Info(err)
code = e.ErrorDatabase
return err
}
return nil
})
if err != nil {
return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
Error: err.Error(),
}
}

return serializer.Response{
Status: code,
Msg: e.GetMsg(code),
}
}