当前位置:网站首页>19、商品微服务-srv层实现
19、商品微服务-srv层实现
2022-08-02 11:04:00 【无休止符】
前言
- 之前我们已经通过grpc从零开始到实现了用户的微服务,接来我们将实现商品微服务
一、需求分析
1 - 数据库实体分析
2 - 商品微服务接口分析
3 - 商品服务表结构设计
- goods_srv/model/base.go:公共的base字段;自定义的GormList类型
package model
import (
"database/sql/driver"
"encoding/json"
"gorm.io/gorm"
"time"
)
type BaseModel struct {
ID int32 `gorm:"primarykey;type:int" json:"id"` //为什么使用int32, bigint
CreatedAt time.Time `gorm:"column:add_time" json:"-"`
UpdatedAt time.Time `gorm:"column:update_time" json:"-"`
DeletedAt gorm.DeletedAt `json:"-"`
IsDeleted bool `json:"-"`
}
// GormList 自定义gorm类型
type GormList []string
func (g GormList) Value() (driver.Value, error) {
return json.Marshal(g)
}
// Scan 实现 sql.Scanner 接口,Scan 将 value 扫描至 Jsonb
func (g *GormList) Scan(value interface{
}) error {
return json.Unmarshal(value.([]byte), &g)
}
- goods_srv/model/goods.go:商品表结构类型定义
package model
// Category
//类型, 这个字段是否能为null, 这个字段应该设置为可以为null还是设置为空, 0
//实际开发过程中,尽量设置为不为null
//https://zhuanlan.zhihu.com/p/73997266
//这些类型我们使用int32还是int,可以减少proto的类型转换,proto没有int类型
type Category struct {
BaseModel
Name string `gorm:"type:varchar(20);not null" json:"name"`
ParentCategoryID int32 `json:"parent"` // 数据库存储的外键ID
ParentCategory *Category `json:"-"` // gorm中外键自己指向自己,需要使用指针
SubCategory []*Category `gorm:"foreignKey:ParentCategoryID;references:ID" json:"sub_category"`
Level int32 `gorm:"type:int;not null;default:1;comment:'1为1级类目,2为2级...'" json:"level"`
IsTab bool `gorm:"default:false;not null;comment:'能否展示在Tab栏'" json:"is_tab"`
}
type Brands struct {
BaseModel
Name string `gorm:"type:varchar(20);not null;comment:'品牌名称'"`
Logo string `gorm:"type:varchar(200);default:'';not null;comment:'品牌图标'"`
}
// GoodsCategoryBrand 商品和品牌的对应关系
// CategoryID和BrandsID使用相同的index,gorm就会建成联合的索引
type GoodsCategoryBrand struct {
BaseModel
CategoryID int32 `gorm:"type:int;index:idx_category_brand,unique"` // 商品外键
Category Category
BrandsID int32 `gorm:"type:int;index:idx_category_brand,unique"` // 品牌外键
Brands Brands
}
// TableName 自定义生成的表名
// 为了让gorm生成GoodsCategoryBrand表的时候不生成下划线的方式
// 如goods_category_brand
func (GoodsCategoryBrand) TableName() string {
return "goodscategorybrand"
}
// Banner 轮播图
type Banner struct {
BaseModel
Image string `gorm:"type:varchar(200);not null"`
Url string `gorm:"type:varchar(200);not null"`
Index int32 `gorm:"type:int;default:1;not null"`
}
// Goods 商品表结构
type Goods struct {
BaseModel
CategoryID int32 `gorm:"type:int;not null"`
Category Category
BrandsID int32 `gorm:"type:int;not null"`
Brands Brands
OnSale bool `gorm:"default:false;not null;comment:'是否上架'"`
ShipFree bool `gorm:"default:false;not null;comment:'是否免运费'"`
IsNew bool `gorm:"default:false;not null;comment:'是否新品'"`
IsHot bool `gorm:"default:false;not null;comment:'是否热卖商品'"`
Name string `gorm:"type:varchar(50);not null"`
GoodsSn string `gorm:"type:varchar(50);not null;comment:'商家的内部编号'"`
ClickNum int32 `gorm:"type:int;default:0;not null;comment:'点击数'"`
SoldNum int32 `gorm:"type:int;default:0;not null;comment:'销售量'"`
FavNum int32 `gorm:"type:int;default:0;not null;comment:'收藏数'"`
MarketPrice float32 `gorm:"not null;comment:'商品价格'"`
ShopPrice float32 `gorm:"not null;comment:'实际价格'"`
GoodsBrief string `gorm:"type:varchar(100);not null;comment:'商品简介'"`
Images GormList `gorm:"type:varchar(1000);not null;comment:'商品图片'"`
DescImages GormList `gorm:"type:varchar(1000);not null;comment:'商品图片'"`
GoodsFrontImage string `gorm:"type:varchar(200);not null;comment:'商品展示图'"`
}
4 - 生成表结构与数据导入
- 新建数据库:mxshop_goods_srv
- goods_srv/model/main/main.go:ip和数据库账号密码自行修改
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
"log"
"nd/goods_srv/model"
"os"
"time"
)
func main() {
dsn := "root:[email protected](192.168.124.51:3306)/mxshop_goods_srv?charset=utf8mb4&parseTime=True&loc=Local"
newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
logger.Config{
SlowThreshold: time.Second, // 慢 SQL 阈值
LogLevel: logger.Info, // Log level
Colorful: true, // 禁用彩色打印
},
)
// 全局模式
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true,
},
Logger: newLogger,
})
if err != nil {
panic(err)
}
_ = db.AutoMigrate(&model.Category{
},
&model.Brands{
}, &model.GoodsCategoryBrand{
}, &model.Banner{
}, &model.Goods{
})
}
二、启动商品服务
1 - proto接口
- goods_srv/proto/goods.proto
syntax = "proto3";
import "google/protobuf/empty.proto";
option go_package = ".;proto";
service Goods{
//商品接口
rpc GoodsList(GoodsFilterRequest) returns(GoodsListResponse);
//现在用户提交订单有多个商品,你得批量查询商品的信息吧
rpc BatchGetGoods(BatchGoodsIdInfo) returns(GoodsListResponse); //批量获取商品信息
rpc CreateGoods(CreateGoodsInfo) returns (GoodsInfoResponse);
rpc DeleteGoods(DeleteGoodsInfo) returns (google.protobuf.Empty);
rpc UpdateGoods(CreateGoodsInfo) returns (google.protobuf.Empty);
rpc GetGoodsDetail(GoodInfoRequest) returns(GoodsInfoResponse);
//商品分类
rpc GetAllCategorysList(google.protobuf.Empty) returns(CategoryListResponse); //获取所有的分类
//获取子分类
rpc GetSubCategory(CategoryListRequest) returns(SubCategoryListResponse);
rpc CreateCategory(CategoryInfoRequest) returns(CategoryInfoResponse); //新建分类信息
rpc DeleteCategory(DeleteCategoryRequest) returns(google.protobuf.Empty); //删除分类
rpc UpdateCategory(CategoryInfoRequest) returns(google.protobuf.Empty); //修改分类信息
//品牌和轮播图
rpc BrandList(BrandFilterRequest) returns(BrandListResponse); //
rpc CreateBrand(BrandRequest) returns(BrandInfoResponse); //新建品牌信息
rpc DeleteBrand(BrandRequest) returns(google.protobuf.Empty); //删除品牌
rpc UpdateBrand(BrandRequest) returns(google.protobuf.Empty); //修改品牌信息
//轮播图
rpc BannerList(google.protobuf.Empty) returns(BannerListResponse); //获取轮播列表信息
rpc CreateBanner(BannerRequest) returns(BannerResponse); //添加banner图
rpc DeleteBanner(BannerRequest) returns(google.protobuf.Empty); //删除轮播图
rpc UpdateBanner(BannerRequest) returns(google.protobuf.Empty); //修改轮播图
//品牌分类
rpc CategoryBrandList(CategoryBrandFilterRequest) returns(CategoryBrandListResponse); //获取轮播列表信息
//通过category获取brands
rpc GetCategoryBrandList(CategoryInfoRequest) returns(BrandListResponse);
rpc CreateCategoryBrand(CategoryBrandRequest) returns(CategoryBrandResponse); //添加banner图
rpc DeleteCategoryBrand(CategoryBrandRequest) returns(google.protobuf.Empty); //删除轮播图
rpc UpdateCategoryBrand(CategoryBrandRequest) returns(google.protobuf.Empty); //修改轮播图
}
message CategoryListRequest {
int32 id = 1;
int32 level = 2;
}
message CategoryInfoRequest {
int32 id = 1;
string name = 2;
int32 parentCategory = 3;
int32 level = 4;
bool isTab = 5;
}
message DeleteCategoryRequest {
int32 id = 1;
}
message QueryCategoryRequest {
int32 id = 1;
string name = 2;
}
message CategoryInfoResponse {
int32 id = 1;
string name = 2;
int32 parentCategory = 3;
int32 level = 4;
bool isTab = 5;
}
message CategoryListResponse {
int32 total = 1;
repeated CategoryInfoResponse data = 2;
string jsonData = 3;
}
message SubCategoryListResponse {
int32 total = 1;
CategoryInfoResponse info = 2;
repeated CategoryInfoResponse subCategorys = 3;
}
message CategoryBrandFilterRequest {
int32 pages = 1;
int32 pagePerNums = 2;
}
message FilterRequest {
int32 pages = 1;
int32 pagePerNums = 2;
}
message CategoryBrandRequest{
int32 id = 1;
int32 categoryId = 2;
int32 brandId = 3;
}
message CategoryBrandResponse{
int32 id = 1;
BrandInfoResponse brand = 2;
CategoryInfoResponse category = 3;
}
message BannerRequest {
int32 id = 1;
int32 index = 2;
string image = 3;
string url = 4;
}
message BannerResponse {
int32 id = 1;
int32 index = 2;
string image = 3;
string url = 4;
}
message BrandFilterRequest {
int32 pages = 1;
int32 pagePerNums = 2;
}
message BrandRequest {
int32 id = 1;
string name = 2;
string logo = 3;
}
message BrandInfoResponse {
int32 id = 1;
string name = 2;
string logo = 3;
}
message BrandListResponse {
int32 total = 1;
repeated BrandInfoResponse data = 2;
}
message BannerListResponse {
int32 total = 1;
repeated BannerResponse data = 2;
}
message CategoryBrandListResponse {
int32 total = 1;
repeated CategoryBrandResponse data = 2;
}
message BatchGoodsIdInfo {
repeated int32 id = 1;
}
message DeleteGoodsInfo {
int32 id = 1;
}
message CategoryBriefInfoResponse {
int32 id = 1;
string name = 2;
}
message CategoryFilterRequest {
int32 id = 1;
bool isTab = 2;
}
message GoodInfoRequest {
int32 id = 1;
}
message CreateGoodsInfo {
int32 id = 1;
string name = 2;
string goodsSn = 3;
int32 stocks = 7; //库存,
float marketPrice = 8;
float shopPrice = 9;
string goodsBrief = 10;
string goodsDesc = 11;
bool shipFree = 12;
repeated string images = 13;
repeated string descImages = 14;
string goodsFrontImage = 15;
bool isNew = 16;
bool isHot = 17;
bool onSale = 18;
int32 categoryId = 19;
int32 brandId = 20;
}
message GoodsReduceRequest {
int32 GoodsId = 1;
int32 nums = 2;
}
message BatchCategoryInfoRequest {
repeated int32 id = 1;
int32 goodsNums = 2;
int32 brandNums = 3;
}
message GoodsFilterRequest {
int32 priceMin = 1;
int32 priceMax = 2;
bool isHot = 3;
bool isNew = 4;
bool isTab = 5;
int32 topCategory = 6;
int32 pages = 7;
int32 pagePerNums = 8;
string keyWords = 9;
int32 brand = 10;
}
message GoodsInfoResponse {
int32 id = 1;
int32 categoryId = 2;
string name = 3;
string goodsSn = 4;
int32 clickNum = 5;
int32 soldNum = 6;
int32 favNum = 7;
float marketPrice = 9;
float shopPrice = 10;
string goodsBrief = 11;
string goodsDesc = 12;
bool shipFree = 13;
repeated string images = 14;
repeated string descImages = 15;
string goodsFrontImage = 16;
bool isNew = 17;
bool isHot = 18;
bool onSale = 19;
int64 addTime = 20;
CategoryBriefInfoResponse category = 21;
BrandInfoResponse brand = 22;
}
message GoodsListResponse {
int32 total = 1;
repeated GoodsInfoResponse data = 2;
}
- 生成pb文件:
protoc --go_out=. --go_opt=paths=import --go-grpc_out=. --go-grpc_opt=paths=import *.proto
2 - handle中实现接口
- goods_srv/handler/handle_goods.go:这里我们可以先不用实现,使用
proto.UnimplementedGoodsServer
package handler
import (
"context"
"google.golang.org/protobuf/types/known/emptypb"
"nd/goods_srv/proto"
)
type GoodsServer struct {
proto.UnimplementedGoodsServer
}
3 - nacos配置
- 添加命名空间goods
- 克隆之前user_srv的配置到goods_srv中
- 编辑goods_srv.json的配置:将Tags配置也放入,以及本机的Host也加入配置
4 - 修改yaml配置
- 修改命名空间id与dataid
//goods_srv/config_debug.yaml
host: '192.168.124.51'
port: 8848
namespace: 'b2f00e3b-4e22-4e87-8129-9c02b5f1220d'
user: 'nacos'
password: 'nacos'
dataid: 'goods_srv.json'
group: 'dev'
//goods_srv/config_pro.yaml
host: '192.168.124.51'
port: 8848
namespace: 'b2f00e3b-4e22-4e87-8129-9c02b5f1220d'
user: 'nacos'
password: 'nacos'
dataid: 'goods_srv.json'
group: 'pro'
5 - 读取Tags和本机host
- goods_srv/config/config.go:ServerConfig中添加Tags
package config
type MysqlConfig struct {
Host string `mapstructure:"host" json:"host"`
Port int `mapstructure:"port" json:"port"`
Name string `mapstructure:"db" json:"db"`
User string `mapstructure:"user" json:"user"`
Password string `mapstructure:"password" json:"password"`
}
type ConsulConfig struct {
Host string `mapstructure:"host" json:"host"`
Port int `mapstructure:"port" json:"port"`
}
type ServerConfig struct {
Name string `mapstructure:"name" json:"name"`
Tags []string `mapstructure:"tags" json:"tags"`
MysqlInfo MysqlConfig `mapstructure:"mysql" json:"mysql"`
ConsulInfo ConsulConfig `mapstructure:"consul" json:"consul"`
}
type NacosConfig struct {
Host string `mapstructure:"host"`
Port uint64 `mapstructure:"port"`
Namespace string `mapstructure:"namespace"`
User string `mapstructure:"user"`
Password string `mapstructure:"password"`
DataId string `mapstructure:"dataid"`
Group string `mapstructure:"group"`
}
- goods_srv/main.go:
- 注册时使用:
registration.Tags = global.ServerConfig.Tags
- 健康检查与服务注册的host使用:
global.ServerConfig.Host
- 注册时使用:
package main
import (
"flag"
"fmt"
"github.com/hashicorp/consul/api"
"github.com/satori/go.uuid"
"go.uber.org/zap"
"google.golang.org/grpc/health"
"google.golang.org/grpc/health/grpc_health_v1"
"nd/goods_srv/global"
"nd/goods_srv/handler"
"nd/goods_srv/initialize"
"nd/goods_srv/proto"
"nd/goods_srv/utils"
"net"
"os"
"os/signal"
"syscall"
"google.golang.org/grpc"
)
func main() {
IP := flag.String("ip", "0.0.0.0", "ip地址")
Port := flag.Int("port", 0, "端口号") // 这个修改为0,如果我们从命令行带参数启动的话就不会为0
//初始化
initialize.InitLogger()
initialize.InitConfig()
initialize.InitDB()
zap.S().Info(global.ServerConfig)
flag.Parse()
zap.S().Info("ip: ", *IP)
if *Port == 0 {
*Port, _ = utils.GetFreePort()
}
zap.S().Info("port: ", *Port)
server := grpc.NewServer()
proto.RegisterGoodsServer(server, &handler.GoodsServer{
})
lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", *IP, *Port))
if err != nil {
panic("failed to listen:" + err.Error())
}
//注册服务健康检查
grpc_health_v1.RegisterHealthServer(server, health.NewServer())
//服务注册
cfg := api.DefaultConfig()
cfg.Address = fmt.Sprintf("%s:%d", global.ServerConfig.ConsulInfo.Host,
global.ServerConfig.ConsulInfo.Port)
client, err := api.NewClient(cfg)
if err != nil {
panic(err)
}
//生成对应的检查对象
check := &api.AgentServiceCheck{
GRPC: fmt.Sprintf("%s:%d", global.ServerConfig.Host, *Port),
Timeout: "5s",
Interval: "5s",
DeregisterCriticalServiceAfter: "15s",
}
//生成注册对象
registration := new(api.AgentServiceRegistration)
registration.Name = global.ServerConfig.Name
serviceID := fmt.Sprintf("%s", uuid.NewV4())
registration.ID = serviceID
registration.Port = *Port
registration.Tags = global.ServerConfig.Tags
registration.Address = global.ServerConfig.Host
registration.Check = check
err = client.Agent().ServiceRegister(registration)
if err != nil {
panic(err)
}
go func() {
err = server.Serve(lis)
if err != nil {
panic("failed to start grpc:" + err.Error())
}
}()
//接收终止信号
quit := make(chan os.Signal)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
if err = client.Agent().ServiceDeregister(serviceID); err != nil {
zap.S().Info("注销失败")
}
zap.S().Info("注销成功")
}
三、品牌、商品分类、轮播接口
1 - 接口测试
- goods_srv/main.go:为了让测试的时候端口不会变化,我们在main中修改port为50058
func main() {
IP := flag.String("ip", "0.0.0.0", "ip地址")
Port := flag.Int("port", 50058, "端口号")
//...省略
- goods_srv/tests/test_brands.go:添加测试方法
package main
import (
"context"
"fmt"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc"
"nd/goods_srv/proto"
)
var brandClient proto.GoodsClient
var conn *grpc.ClientConn
func TestGetBrandList() {
rsp, err := brandClient.BrandList(context.Background(), &proto.BrandFilterRequest{
})
if err != nil {
panic(err)
}
fmt.Println(rsp.Total)
for _, brand := range rsp.Data {
fmt.Println(brand.Name)
}
}
func Init() {
var err error
conn, err = grpc.Dial("127.0.0.1:50058", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic(err)
}
brandClient = proto.NewGoodsClient(conn)
}
func main() {
Init()
TestGetBrandList()
conn.Close()
}
- 因为之前使用的是proto.UnimplementedGoodsServer:我们期望的返回是rpc接口未定义
2 - 品牌列表查询实现
- goods_srv/handler/handle_base.go:添加分页的逻辑
package handler
import "gorm.io/gorm"
func Paginate(page, pageSize int) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
if page == 0 {
page = 1
}
switch {
case pageSize > 100:
pageSize = 100
case pageSize <= 0:
pageSize = 10
}
offset := (page - 1) * pageSize
return db.Offset(offset).Limit(pageSize)
}
}
- goods_srv/handler/handle_brands.go:实现BrandList接口
package handler
import (
"context"
"nd/goods_srv/global"
"nd/goods_srv/model"
"nd/goods_srv/proto"
)
// BrandList 品牌和轮播图
func (s *GoodsServer) BrandList(ctx context.Context, req *proto.BrandFilterRequest) (*proto.BrandListResponse, error) {
brandListResponse := proto.BrandListResponse{
}
var brands []model.Brands
result := global.DB.Scopes(Paginate(int(req.Pages), int(req.PagePerNums))).Find(&brands)
if result.Error != nil {
return nil, result.Error
}
var total int64
global.DB.Model(&model.Brands{
}).Count(&total)
brandListResponse.Total = int32(total)
var brandResponses []*proto.BrandInfoResponse
for _, brand := range brands {
brandResponses = append(brandResponses, &proto.BrandInfoResponse{
Id: brand.ID,
Name: brand.Name,
Logo: brand.Logo,
})
}
brandListResponse.Data = brandResponses
return &brandListResponse, nil
}
//CreateBrand(ctx context.Context, in *BrandRequest, opts ...grpc.CallOption) (*BrandInfoResponse, error)
//DeleteBrand(ctx context.Context, in *BrandRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
//UpdateBrand(ctx context.Context, in *BrandRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
- goods_srv/tests/test_brands.go:测试接口实现
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"nd/goods_srv/proto"
)
var brandClient proto.GoodsClient
var conn *grpc.ClientConn
func TestGetBrandList() {
rsp, err := brandClient.BrandList(context.Background(), &proto.BrandFilterRequest{
})
if err != nil {
panic(err)
}
fmt.Println(rsp.Total)
for _, brand := range rsp.Data {
fmt.Println(brand.Name)
}
}
func Init() {
var err error
conn, err = grpc.Dial("127.0.0.1:50058", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic(err)
}
brandClient = proto.NewGoodsClient(conn)
}
func main() {
Init()
TestGetBrandList()
conn.Close()
}
3 - 品牌新建、删除、更新接口
- goods_srv/handler/handle_brands.go:实现CreateBrand、DeleteBrand、UpdateBrand
package handler
import (
"context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
"nd/goods_srv/global"
"nd/goods_srv/model"
"nd/goods_srv/proto"
)
// BrandList 品牌和轮播图
func (s *GoodsServer) BrandList(ctx context.Context, req *proto.BrandFilterRequest) (*proto.BrandListResponse, error) {
brandListResponse := proto.BrandListResponse{
}
var brands []model.Brands
result := global.DB.Scopes(Paginate(int(req.Pages), int(req.PagePerNums))).Find(&brands)
if result.Error != nil {
return nil, result.Error
}
var total int64
global.DB.Model(&model.Brands{
}).Count(&total)
brandListResponse.Total = int32(total)
var brandResponses []*proto.BrandInfoResponse
for _, brand := range brands {
brandResponses = append(brandResponses, &proto.BrandInfoResponse{
Id: brand.ID,
Name: brand.Name,
Logo: brand.Logo,
})
}
brandListResponse.Data = brandResponses
return &brandListResponse, nil
}
func (s *GoodsServer) CreateBrand(ctx context.Context, req *proto.BrandRequest) (*proto.BrandInfoResponse, error) {
//新建品牌 需要先查询品牌是否已存在
if result := global.DB.Where("name=?", req.Name).First(&model.Brands{
}); result.RowsAffected == 1 {
return nil, status.Errorf(codes.InvalidArgument, "品牌已存在")
}
brand := &model.Brands{
Name: req.Name,
Logo: req.Logo,
}
global.DB.Save(brand)
return &proto.BrandInfoResponse{
Id: brand.ID}, nil
}
func (s *GoodsServer) DeleteBrand(ctx context.Context, req *proto.BrandRequest) (*emptypb.Empty, error) {
if result := global.DB.Delete(&model.Brands{
}, req.Id); result.RowsAffected == 0 {
return nil, status.Errorf(codes.NotFound, "品牌不存在")
}
return &emptypb.Empty{
}, nil
}
func (s *GoodsServer) UpdateBrand(ctx context.Context, req *proto.BrandRequest) (*emptypb.Empty, error) {
brands := model.Brands{
}
if result := global.DB.First(&brands); result.RowsAffected == 0 {
return nil, status.Errorf(codes.InvalidArgument, "品牌不存在")
}
if req.Name != "" {
brands.Name = req.Name
}
if req.Logo != "" {
brands.Logo = req.Logo
}
global.DB.Save(&brands)
return &emptypb.Empty{
}, nil
}
4 - 轮播图的CRUD
- goods_srv/handler/handle_banner.go
package handler
import (
"context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
"nd/goods_srv/global"
"nd/goods_srv/model"
"nd/goods_srv/proto"
)
// BannerList 轮播图,不需要分页
func (s *GoodsServer) BannerList(ctx context.Context, req *emptypb.Empty) (*proto.BannerListResponse, error) {
bannerListResponse := proto.BannerListResponse{
}
var banners []model.Banner
result := global.DB.Find(&banners)
bannerListResponse.Total = int32(result.RowsAffected)
var bannerReponses []*proto.BannerResponse
for _, banner := range banners {
bannerReponses = append(bannerReponses, &proto.BannerResponse{
Id: banner.ID,
Image: banner.Image,
Index: banner.Index,
Url: banner.Url,
})
}
bannerListResponse.Data = bannerReponses
return &bannerListResponse, nil
}
func (s *GoodsServer) CreateBanner(ctx context.Context, req *proto.BannerRequest) (*proto.BannerResponse, error) {
banner := model.Banner{
}
banner.Image = req.Image
banner.Index = req.Index
banner.Url = req.Url
global.DB.Save(&banner)
return &proto.BannerResponse{
Id: banner.ID}, nil
}
func (s *GoodsServer) DeleteBanner(ctx context.Context, req *proto.BannerRequest) (*emptypb.Empty, error) {
if result := global.DB.Delete(&model.Banner{
}, req.Id); result.RowsAffected == 0 {
return nil, status.Errorf(codes.NotFound, "轮播图不存在")
}
return &emptypb.Empty{
}, nil
}
func (s *GoodsServer) UpdateBanner(ctx context.Context, req *proto.BannerRequest) (*emptypb.Empty, error) {
var banner model.Banner
if result := global.DB.First(&banner, req.Id); result.RowsAffected == 0 {
return nil, status.Errorf(codes.NotFound, "轮播图不存在")
}
if req.Url != "" {
banner.Url = req.Url
}
if req.Image != "" {
banner.Image = req.Image
}
if req.Index != 0 {
banner.Index = req.Index
}
global.DB.Save(&banner)
return &emptypb.Empty{
}, nil
}
5 - 商品分类列表接口
- goods_srv/handler/handle_category.go
// GetAllCategorysList 商品分类
func (s *GoodsServer) GetAllCategorysList(context.Context, *emptypb.Empty) (*proto.CategoryListResponse, error) {
/* 获取分类的时候构造好返回的json对象,供前端使用 所以在CategoryListResponse结构中专门定义了JsonData用于返回给前端使用 为什么在srv层来实现,因为srv层有gorm,而web层没有gorm并不与数据库交互 如果在web层实现,没有gorm处理起来会比较复杂,所以建议放在srv层来实现 [ { "id":xxx, "name":"", "level":1, "is_tab":false, "parent":13xxx, "sub_category":[ "id":xxx, "name":"", "level":1, "is_tab":false, "sub_category":[] ] } ] */
var categorys []model.Category
// SubCategory []*Category `gorm:"foreignKey:ParentCategoryID;references:ID" json:"sub_category"`
// 配置指明了外键后,可以使用Preload预加载,来把品牌的子分类也取出来
global.DB.Where(&model.Category{
Level: 1}).Preload("SubCategory.SubCategory").Find(&categorys)
b, _ := json.Marshal(&categorys)
return &proto.CategoryListResponse{
JsonData: string(b)}, nil
}
- goods_srv/tests/category/test_category.go:添加测试方法
package main
import (
"context"
"fmt"
"github.com/golang/protobuf/ptypes/empty"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"nd/goods_srv/tests"
"nd/goods_srv/proto"
)
var brandClient proto.GoodsClient
var conn *grpc.ClientConn
func TestGetCategoryList() {
rsp, err := brandClient.GetAllCategorysList(context.Background(), &empty.Empty{
})
if err != nil {
panic(err)
}
fmt.Println(rsp.Total)
fmt.Println(rsp.JsonData)
}
func Init() {
var err error
conn, err = grpc.Dial(tests.TargetAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic(err)
}
brandClient = proto.NewGoodsClient(conn)
}
func main() {
Init()
TestGetCategoryList()
//TestGetSubCategoryList()
conn.Close()
}
6 - 商品分类子分类接口
- goods_srv/handler/handle_category.go:
// GetSubCategory 获取子分类
func (s *GoodsServer) GetSubCategory(ctx context.Context, req *proto.CategoryListRequest) (*proto.SubCategoryListResponse, error) {
categoryListResponse := proto.SubCategoryListResponse{
}
var category model.Category
if result := global.DB.First(&category, req.Id); result.RowsAffected == 0 {
return nil, status.Errorf(codes.NotFound, "商品分类不存在")
}
categoryListResponse.Info = &proto.CategoryInfoResponse{
Id: category.ID,
Name: category.Name,
Level: category.Level,
IsTab: category.IsTab,
ParentCategory: category.ParentCategoryID,
}
var subCategorys []model.Category
var subCategoryResponse []*proto.CategoryInfoResponse
//preloads := "SubCategory"
//if category.Level == 1 {
// preloads = "SubCategory.SubCategory"
//}
global.DB.Where(&model.Category{
ParentCategoryID: req.Id}).Find(&subCategorys)
for _, subCategory := range subCategorys {
subCategoryResponse = append(subCategoryResponse, &proto.CategoryInfoResponse{
Id: subCategory.ID,
Name: subCategory.Name,
Level: subCategory.Level,
IsTab: subCategory.IsTab,
ParentCategory: subCategory.ParentCategoryID,
})
}
categoryListResponse.SubCategorys = subCategoryResponse
return &categoryListResponse, nil
}
- goods_srv/tests/category/test_category.go
func TestGetSubCategoryList() {
rsp, err := brandClient.GetSubCategory(context.Background(), &proto.CategoryListRequest{
Id: 1001,
})
if err != nil {
panic(err)
}
fmt.Println(rsp.SubCategorys)
}
7 - 商品新建、删除和更新接口
- goods_srv/handler/handle_category.go
func (s *GoodsServer) CreateCategory(ctx context.Context, req *proto.CategoryInfoRequest) (*proto.CategoryInfoResponse, error) {
category := model.Category{
}
cMap := map[string]interface{
}{
}
cMap["name"] = req.Name
cMap["level"] = req.Level
cMap["is_tab"] = req.IsTab
if req.Level != 1 {
//去查询父类目是否存在
cMap["parent_category_id"] = req.ParentCategory
}
tx := global.DB.Model(&model.Category{
}).Create(cMap)
fmt.Println(tx)
return &proto.CategoryInfoResponse{
Id: int32(category.ID)}, nil
}
func (s *GoodsServer) DeleteCategory(ctx context.Context, req *proto.DeleteCategoryRequest) (*emptypb.Empty, error) {
if result := global.DB.Delete(&model.Category{
}, req.Id); result.RowsAffected == 0 {
return nil, status.Errorf(codes.NotFound, "商品分类不存在")
}
return &emptypb.Empty{
}, nil
}
func (s *GoodsServer) UpdateCategory(ctx context.Context, req *proto.CategoryInfoRequest) (*emptypb.Empty, error) {
var category model.Category
if result := global.DB.First(&category, req.Id); result.RowsAffected == 0 {
return nil, status.Errorf(codes.NotFound, "商品分类不存在")
}
if req.Name != "" {
category.Name = req.Name
}
if req.ParentCategory != 0 {
category.ParentCategoryID = req.ParentCategory
}
if req.Level != 0 {
category.Level = req.Level
}
if req.IsTab {
category.IsTab = req.IsTab
}
global.DB.Save(&category)
return &emptypb.Empty{
}, nil
}
8 - 品牌分类相关接口
- goods_srv/handler/handle_category_brand.go
package handler
import (
"context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
"nd/goods_srv/global"
"nd/goods_srv/model"
"nd/goods_srv/proto"
)
// CategoryBrandList 品牌分类
func (s *GoodsServer) CategoryBrandList(ctx context.Context, req *proto.CategoryBrandFilterRequest) (*proto.CategoryBrandListResponse, error) {
var categoryBrands []model.GoodsCategoryBrand
categoryBrandListResponse := proto.CategoryBrandListResponse{
}
var total int64
global.DB.Model(&model.GoodsCategoryBrand{
}).Count(&total)
categoryBrandListResponse.Total = int32(total)
global.DB.Preload("Category").Preload("Brands").Scopes(Paginate(int(req.Pages), int(req.PagePerNums))).Find(&categoryBrands)
var categoryResponses []*proto.CategoryBrandResponse
for _, categoryBrand := range categoryBrands {
categoryResponses = append(categoryResponses, &proto.CategoryBrandResponse{
Category: &proto.CategoryInfoResponse{
Id: categoryBrand.Category.ID,
Name: categoryBrand.Category.Name,
Level: categoryBrand.Category.Level,
IsTab: categoryBrand.Category.IsTab,
ParentCategory: categoryBrand.Category.ParentCategoryID,
},
Brand: &proto.BrandInfoResponse{
Id: categoryBrand.Brands.ID,
Name: categoryBrand.Brands.Name,
Logo: categoryBrand.Brands.Logo,
},
})
}
categoryBrandListResponse.Data = categoryResponses
return &categoryBrandListResponse, nil
}
func (s *GoodsServer) GetCategoryBrandList(ctx context.Context, req *proto.CategoryInfoRequest) (*proto.BrandListResponse, error) {
brandListResponse := proto.BrandListResponse{
}
var category model.Category
if result := global.DB.Find(&category, req.Id).First(&category); result.RowsAffected == 0 {
return nil, status.Errorf(codes.InvalidArgument, "商品分类不存在")
}
var categoryBrands []model.GoodsCategoryBrand
if result := global.DB.Preload("Brands").Where(&model.GoodsCategoryBrand{
CategoryID: req.Id}).Find(&categoryBrands); result.RowsAffected > 0 {
brandListResponse.Total = int32(result.RowsAffected)
}
var brandInfoResponses []*proto.BrandInfoResponse
for _, categoryBrand := range categoryBrands {
brandInfoResponses = append(brandInfoResponses, &proto.BrandInfoResponse{
Id: categoryBrand.Brands.ID,
Name: categoryBrand.Brands.Name,
Logo: categoryBrand.Brands.Logo,
})
}
brandListResponse.Data = brandInfoResponses
return &brandListResponse, nil
}
func (s *GoodsServer) CreateCategoryBrand(ctx context.Context, req *proto.CategoryBrandRequest) (*proto.CategoryBrandResponse, error) {
var category model.Category
if result := global.DB.First(&category, req.CategoryId); result.RowsAffected == 0 {
return nil, status.Errorf(codes.InvalidArgument, "商品分类不存在")
}
var brand model.Brands
if result := global.DB.First(&brand, req.BrandId); result.RowsAffected == 0 {
return nil, status.Errorf(codes.InvalidArgument, "品牌不存在")
}
categoryBrand := model.GoodsCategoryBrand{
CategoryID: req.CategoryId,
BrandsID: req.BrandId,
}
global.DB.Save(&categoryBrand)
return &proto.CategoryBrandResponse{
Id: categoryBrand.ID}, nil
}
func (s *GoodsServer) DeleteCategoryBrand(ctx context.Context, req *proto.CategoryBrandRequest) (*emptypb.Empty, error) {
if result := global.DB.Delete(&model.GoodsCategoryBrand{
}, req.Id); result.RowsAffected == 0 {
return nil, status.Errorf(codes.NotFound, "品牌分类不存在")
}
return &emptypb.Empty{
}, nil
}
func (s *GoodsServer) UpdateCategoryBrand(ctx context.Context, req *proto.CategoryBrandRequest) (*emptypb.Empty, error) {
var categoryBrand model.GoodsCategoryBrand
if result := global.DB.First(&categoryBrand, req.Id); result.RowsAffected == 0 {
return nil, status.Errorf(codes.InvalidArgument, "品牌分类不存在")
}
var category model.Category
if result := global.DB.First(&category, req.CategoryId); result.RowsAffected == 0 {
return nil, status.Errorf(codes.InvalidArgument, "商品分类不存在")
}
var brand model.Brands
if result := global.DB.First(&brand, req.BrandId); result.RowsAffected == 0 {
return nil, status.Errorf(codes.InvalidArgument, "品牌不存在")
}
categoryBrand.CategoryID = req.CategoryId
categoryBrand.BrandsID = req.BrandId
global.DB.Save(&categoryBrand)
return &emptypb.Empty{
}, nil
}
- goods_srv/tests/category_brand/test_category_brand.go:测试接口
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"nd/goods_srv/proto"
"nd/goods_srv/tests"
)
var brandClient proto.GoodsClient
var conn *grpc.ClientConn
func TestGetCategoryBrandList() {
rsp, err := brandClient.CategoryBrandList(context.Background(), &proto.CategoryBrandFilterRequest{
})
if err != nil {
panic(err)
}
fmt.Println(rsp.Total)
fmt.Println(rsp.Data)
}
func Init() {
var err error
conn, err = grpc.Dial(tests.TargetAddr, grpc.WithInsecure())
if err != nil {
panic(err)
}
brandClient = proto.NewGoodsClient(conn)
}
func main() {
Init()
TestGetCategoryBrandList()
conn.Close()
}
四、商品接口
1 - 商品列表接口实现
- goods_srv/handler/handle_goods.go:根据条件筛选查询商品
package handler
import (
"context"
"fmt"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"nd/goods_srv/global"
"nd/goods_srv/model"
"nd/goods_srv/proto"
)
type GoodsServer struct {
proto.UnimplementedGoodsServer
}
func ModelToResponse(goods model.Goods) proto.GoodsInfoResponse {
return proto.GoodsInfoResponse{
Id: goods.ID,
CategoryId: goods.CategoryID,
Name: goods.Name,
GoodsSn: goods.GoodsSn,
ClickNum: goods.ClickNum,
SoldNum: goods.SoldNum,
FavNum: goods.FavNum,
MarketPrice: goods.MarketPrice,
ShopPrice: goods.ShopPrice,
GoodsBrief: goods.GoodsBrief,
ShipFree: goods.ShipFree,
GoodsFrontImage: goods.GoodsFrontImage,
IsNew: goods.IsNew,
IsHot: goods.IsHot,
OnSale: goods.OnSale,
DescImages: goods.DescImages,
Images: goods.Images,
Category: &proto.CategoryBriefInfoResponse{
Id: goods.Category.ID,
Name: goods.Category.Name,
},
Brand: &proto.BrandInfoResponse{
Id: goods.Brands.ID,
Name: goods.Brands.Name,
Logo: goods.Brands.Logo,
},
}
}
func (s *GoodsServer) GoodsList(ctx context.Context, req *proto.GoodsFilterRequest) (*proto.GoodsListResponse, error) {
//关键词搜索、查询新品、查询热门商品、通过价格区间筛选, 通过商品分类筛选
goodsListResponse := &proto.GoodsListResponse{
}
var goods []model.Goods
// 不要修改全局的DB,而是使用局部的localDB
localDB := global.DB.Model(model.Goods{
})
// 这里开始拼接查询语句
if req.KeyWords != "" {
localDB = localDB.Where("name LIKE ?", "%"+req.KeyWords+"%")
}
if req.IsHot {
localDB = localDB.Where(model.Goods{
IsHot: true})
}
if req.IsNew {
localDB = localDB.Where(model.Goods{
IsNew: true})
}
if req.PriceMin > 0 {
localDB = localDB.Where("shop_price >= ?", req.PriceMin)
}
if req.PriceMax > 0 {
localDB = localDB.Where("shop_price <= ?", req.PriceMax)
}
if req.Brand > 0 {
localDB = localDB.Where("brand_id = ?", req.Brand)
}
//通过category去查询商品
// 子查询 嵌套 子查询
// SELECT * FROM goods WHERE category_id IN(SELECT id FROM category WHERE parent_category_id IN (SELECT id FROM category WHERE parent_category_id=1001))
var subQuery string
if req.TopCategory > 0 {
var category model.Category
if result := global.DB.First(&category, req.TopCategory); result.RowsAffected == 0 {
return nil, status.Errorf(codes.NotFound, "商品分类不存在")
}
if category.Level == 1 {
subQuery = fmt.Sprintf("select id from category where parent_category_id in (select id from category WHERE parent_category_id=%d)", req.TopCategory)
} else if category.Level == 2 {
subQuery = fmt.Sprintf("select id from category WHERE parent_category_id=%d", req.TopCategory)
} else if category.Level == 3 {
subQuery = fmt.Sprintf("select id from category WHERE id=%d", req.TopCategory)
}
localDB = localDB.Where(fmt.Sprintf("category_id in (%s)", subQuery))
}
var count int64
localDB.Count(&count)
goodsListResponse.Total = int32(count) //这个total的总数量一定要在分页之前完成,否则就是分页的数量了
// 有外键需要使用 Preload
result := localDB.Preload("Category").Preload("Brands").Scopes(Paginate(int(req.Pages), int(req.PagePerNums))).Find(&goods)
if result.Error != nil {
return nil, result.Error
}
for _, good := range goods {
goodsInfoResponse := ModelToResponse(good)
goodsListResponse.Data = append(goodsListResponse.Data, &goodsInfoResponse)
}
return goodsListResponse, nil
}
- goods_srv/tests/goods/test_goods.go:goods接口测试
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"nd/goods_srv/proto"
"nd/goods_srv/tests"
)
var brandClient proto.GoodsClient
var conn *grpc.ClientConn
func Init() {
var err error
conn, err = grpc.Dial(tests.TargetAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic(err)
}
brandClient = proto.NewGoodsClient(conn)
}
func TestGetGoodsList() {
rsp, err := brandClient.GoodsList(context.Background(), &proto.GoodsFilterRequest{
//TopCategory: 2007,
PriceMin: 10,
KeyWords: "水",
})
if err != nil {
panic(err)
}
fmt.Println(rsp.Total)
for _, good := range rsp.Data {
fmt.Println(good.Name, good.ShopPrice)
}
}
func main() {
Init()
TestGetGoodsList()
conn.Close()
}
- 为了方便测试,我们先注释掉Images和DescImages
//goods_srv/handler/handle_goods.go
IsHot: goods.IsHot,
OnSale: goods.OnSale,
//DescImages: goods.DescImages,
//Images: goods.Images,
Category: &proto.CategoryBriefInfoResponse{
Id: goods.Category.ID,
Name: goods.Category.Name,
},
//goods_srv/model/goods.go
GoodsBrief string `gorm:"type:varchar(100);not null;comment:'商品简介'"`
//Images GormList `gorm:"type:varchar(1000);not null;comment:'商品图片'"`
//DescImages GormList `gorm:"type:varchar(1000);not null;comment:'商品图片'"`
GoodsFrontImage string `gorm:"type:varchar(200);not null;comment:'商品展示图'"`
2 - 批量获取商品、获取商品详情
- goods_srv/handler/handle_goods.go
// BatchGetGoods 现在用户提交订单有多个商品,你得批量查询商品的信息吧
func (s *GoodsServer) BatchGetGoods(ctx context.Context, req *proto.BatchGoodsIdInfo) (*proto.GoodsListResponse, error) {
goodsListResponse := &proto.GoodsListResponse{
}
var goods []model.Goods
//调用where并不会真正执行sql 只是用来生成sql的 当调用find, first才会去执行sql,
result := global.DB.Where(req.Id).Find(&goods)
for _, good := range goods {
goodsInfoResponse := ModelToResponse(good)
goodsListResponse.Data = append(goodsListResponse.Data, &goodsInfoResponse)
}
goodsListResponse.Total = int32(result.RowsAffected)
return goodsListResponse, nil
}
func (s *GoodsServer) GetGoodsDetail(ctx context.Context, req *proto.GoodInfoRequest) (*proto.GoodsInfoResponse, error) {
var goods model.Goods
if result := global.DB.Preload("Category").Preload("Brands").First(&goods, req.Id); result.RowsAffected == 0 {
return nil, status.Errorf(codes.NotFound, "商品不存在")
}
goodsInfoResponse := ModelToResponse(goods)
return &goodsInfoResponse, nil
}
- goods_srv/tests/goods/test_goods.go
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"nd/goods_srv/proto"
"nd/goods_srv/tests"
)
var brandClient proto.GoodsClient
var conn *grpc.ClientConn
func Init() {
var err error
conn, err = grpc.Dial(tests.TargetAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic(err)
}
brandClient = proto.NewGoodsClient(conn)
}
func TestGetGoodsList() {
rsp, err := brandClient.GoodsList(context.Background(), &proto.GoodsFilterRequest{
//TopCategory: 2007,
PriceMin: 10,
KeyWords: "水",
})
if err != nil {
panic(err)
}
fmt.Println(rsp.Total)
for _, good := range rsp.Data {
fmt.Println(good.Name, good.ShopPrice)
}
}
func TestBatchGetGoods() {
rsp, err := brandClient.BatchGetGoods(context.Background(), &proto.BatchGoodsIdInfo{
Id: []int32{
29, 30, 31},
})
if err != nil {
panic(err)
}
fmt.Println(rsp.Total)
for _, good := range rsp.Data {
fmt.Println(good.Name, good.ShopPrice)
}
}
func TestGetGoodsDetail() {
rsp, err := brandClient.GetGoodsDetail(context.Background(), &proto.GoodInfoRequest{
Id: 28,
})
if err != nil {
panic(err)
}
fmt.Println(rsp.Name)
fmt.Println(rsp.ShopPrice)
fmt.Println(rsp.MarketPrice)
}
func main() {
Init()
//TestGetGoodsList()
TestBatchGetGoods()
fmt.Println("----------------------------")
TestGetGoodsDetail()
conn.Close()
}
3 - 新增、修改、更新商品
- goods_srv/handler/handle_goods.go
func (s *GoodsServer) CreateGoods(ctx context.Context, req *proto.CreateGoodsInfo) (*proto.GoodsInfoResponse, error) {
var category model.Category
if result := global.DB.First(&category, req.CategoryId); result.RowsAffected == 0 {
return nil, status.Errorf(codes.InvalidArgument, "商品分类不存在")
}
var brand model.Brands
if result := global.DB.First(&brand, req.BrandId); result.RowsAffected == 0 {
return nil, status.Errorf(codes.InvalidArgument, "品牌不存在")
}
//先检查redis中是否有这个token
//防止同一个token的数据重复插入到数据库中,如果redis中没有这个token则放入redis
//这里没有看到图片文件是如何上传, 在微服务中 普通的文件上传已经不再使用
goods := model.Goods{
Brands: brand,
BrandsID: brand.ID,
Category: category,
CategoryID: category.ID,
Name: req.Name,
GoodsSn: req.GoodsSn,
MarketPrice: req.MarketPrice,
ShopPrice: req.ShopPrice,
GoodsBrief: req.GoodsBrief,
ShipFree: req.ShipFree,
//Images: req.Images,
//DescImages: req.DescImages,
GoodsFrontImage: req.GoodsFrontImage,
IsNew: req.IsNew,
IsHot: req.IsHot,
OnSale: req.OnSale,
}
//srv之间互相调用了
tx := global.DB.Begin()
result := tx.Save(&goods)
if result.Error != nil {
tx.Rollback()
return nil, result.Error
}
tx.Commit()
return &proto.GoodsInfoResponse{
Id: goods.ID,
}, nil
}
// DeleteGoods 逻辑删除
func (s *GoodsServer) DeleteGoods(ctx context.Context, req *proto.DeleteGoodsInfo) (*emptypb.Empty, error) {
if result := global.DB.Delete(&model.Goods{
BaseModel: model.BaseModel{
ID: req.Id}}, req.Id); result.Error != nil {
return nil, status.Errorf(codes.NotFound, "商品不存在")
}
return &emptypb.Empty{
}, nil
}
func (s *GoodsServer) UpdateGoods(ctx context.Context, req *proto.CreateGoodsInfo) (*emptypb.Empty, error) {
var goods model.Goods
if result := global.DB.First(&goods, req.Id); result.RowsAffected == 0 {
return nil, status.Errorf(codes.NotFound, "商品不存在")
}
var category model.Category
if result := global.DB.First(&category, req.CategoryId); result.RowsAffected == 0 {
return nil, status.Errorf(codes.InvalidArgument, "商品分类不存在")
}
var brand model.Brands
if result := global.DB.First(&brand, req.BrandId); result.RowsAffected == 0 {
return nil, status.Errorf(codes.InvalidArgument, "品牌不存在")
}
goods.Brands = brand
goods.BrandsID = brand.ID
goods.Category = category
goods.CategoryID = category.ID
goods.Name = req.Name
goods.GoodsSn = req.GoodsSn
goods.MarketPrice = req.MarketPrice
goods.ShopPrice = req.ShopPrice
goods.GoodsBrief = req.GoodsBrief
goods.ShipFree = req.ShipFree
//goods.Images= req.Images
//goods.DescImages= req.DescImages
goods.GoodsFrontImage = req.GoodsFrontImage
goods.IsNew = req.IsNew
goods.IsHot = req.IsHot
goods.OnSale = req.OnSale
tx := global.DB.Begin()
result := tx.Save(&goods)
if result.Error != nil {
tx.Rollback()
return nil, result.Error
}
tx.Commit()
return &emptypb.Empty{
}, nil
}
五、完整源码
- 完整源码下载:mxshop_srvsV8.2.rar
- 源码说明:(nacos的ip配置自行修改,全局变量DEV_CONFIG设置:1=zsz,2=comp,3=home)
- goods_srv/model/sql/mxshop_goods.sql:包含了建表语句
- other_import/api.json:YApi的导入文件
- other_import/nacos_config_export_user.zip:nacos的user配置集导入文件
- other_import/nacos_config_export_goods.zip:nacos的goods配置集导入文件
边栏推荐
猜你喜欢
随机推荐
微信小程序---组件开发与使用
How to choose a truly "easy-to-use, high-performance" remote control software
小几届的学弟问我,软件测试岗是选11k的华为还是20k的小公司,我直呼受不了,太凡尔赛了~
LayaBox---TypeScript---Decorator
一体化在线政务服务平台,小程序容器技术加速建设步伐
X86函数调用模型分析
ES2020-23简单易懂又实用的精选特性讲解 日常开发必备干货!
LayaBox---TypeScript---Namespaces and modules
yolo格式(txt)数据集转VOC(xml)
鸿星尔克再捐一个亿
Event 对象,你很了解吗?
ssm网页访问数据库数据报错
Several reasons why applet plugins benefit developers
ansible模块--yum模块
MSYS2 QtCreator Clangd 代码分析找不到 mm_malloc.h的问题补救
LeetCode每日一练 —— 20. 有效的括号
阿里云数据存储生态计划发布,助力伙伴数据创新
企业级数据治理工作怎么开展?Datahub这样做
The sitcom "Re-Walking the Long March" was staged
OSI 七层模型和TCP/IP模型及对应协议(详解)