需求分析:收藏、收货地址管理、留言,我们可以统一成用户的操作,所以我们新建一个服务userop_srv来进行实现
一、快速启动userop_srv
1 - 快速生成项目
- userop_srv:将inventory_srv下除了tmp以外的文件直接拷贝到userop_srv中;将inventory_srv在选中文件夹中替换成userop_srv
2 - 表结构设计
- userop_srv/model/userop.go
package model
const (
LEAVING_MESSAGES = iota + 1
COMPLAINT
INQUIRY
POST_SALE
WANT_TO_BUY
)
type LeavingMessages struct {
BaseModel
User int32 `gorm:"type:int;index"`
MessageType int32 `gorm:"type:int comment '留言类型: 1(留言),2(投诉),3(询问),4(售后),5(求购)'"`
Subject string `gorm:"type:varchar(100)"`
Message string
File string `gorm:"type:varchar(200)"`
}
func (LeavingMessages) TableName() string {
return "leavingmessages"
}
type Address struct {
BaseModel
User int32 `gorm:"type:int;index"`
Province string `gorm:"type:varchar(10)"`
City string `gorm:"type:varchar(10)"`
District string `gorm:"type:varchar(20)"`
Address string `gorm:"type:varchar(100)"`
SignerName string `gorm:"type:varchar(20)"`
SignerMobile string `gorm:"type:varchar(11)"`
}
type UserFav struct {
BaseModel
User int32 `gorm:"type:int;index:idx_user_goods,unique"`
Goods int32 `gorm:"type:int;index:idx_user_goods,unique"`
}
func (UserFav) TableName() string {
return "userfav"
}
-
数据库新建:mxshop_userop_srv
-
userop_srv/model/main/main.go:创建表结构
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
"log"
"nd/userop_srv/global"
"nd/userop_srv/initialize"
"nd/userop_srv/model"
"os"
"time"
)
func main() {
initialize.InitConfig()
dsn := fmt.Sprintf("root:jiushi@tcp(%s:3306)/mxshop_userop_srv?charset=utf8mb4&parseTime=True&loc=Local", global.ServerConfig.MysqlInfo.Host)
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.LeavingMessages{}, &model.UserFav{}, model.Address{})
}
3 - nacos与yaml配置
- 添加nacos配置
- 克隆inventory的配置:对应修改为userop即可
{
"name": "userop_srv",
"host": "192.168.78.1",
"tags": ["imooc", "bobby", "userop", "srv"],
"mysql": {
"host": "192.168.78.131",
"port": 3306,
"user": "root",
"password": "jiushi",
"db": "mxshop_userop_srv"
},
"consul": {
"host": "192.168.78.131",
"port": 8500
}
}
- yaml修改命名空间id
host: '192.168.78.131'
port: 8848
namespace: '167f44f6-8d47-4b13-a01a-e2a570dabaab'
user: 'nacos'
password: 'nacos'
dataid: 'userop_srv.json'
group: 'comp'
4 - proto接口定义
- userop_srv/proto/address.proto
syntax = "proto3";
import "google/protobuf/empty.proto";
option go_package = ".;proto";
service Address{
rpc GetAddressList(AddressRequest) returns(AddressListResponse); //查看地址
rpc CreateAddress(AddressRequest) returns(AddressResponse); //新增地址
rpc DeleteAddress(AddressRequest) returns(google.protobuf.Empty); //删除地址
rpc UpdateAddress(AddressRequest) returns(google.protobuf.Empty); //修改地址
}
message AddressRequest{
int32 id = 1;
int32 userId = 2;
string province = 3;
string city = 4;
string district = 5;
string address = 6;
string signerName = 7;
string signerMobile = 8;
}
message AddressResponse{
int32 id = 1;
int32 userId = 2;
string province = 3;
string city = 4;
string district = 5;
string address = 6;
string signerName = 7;
string signerMobile = 8;
}
message AddressListResponse {
int32 total = 1;
repeated AddressResponse data = 2;
}
- userop_srv/proto/message.proto
syntax = "proto3";
option go_package = ".;proto";
service Message{
rpc MessageList(MessageRequest) returns(MessageListResponse); //批量获取留言信息
rpc CreateMessage(MessageRequest) returns(MessageResponse); //添加留言
}
message MessageRequest{
int32 id = 1;
int32 userId = 2;
int32 messageType = 3;
string subject = 4;
string message = 5;
string file = 6;
}
message MessageResponse{
int32 id = 1;
int32 userId = 2;
int32 messageType = 3;
string subject = 4;
string message = 5;
string file = 6;
}
message MessageListResponse {
int32 total = 1;
repeated MessageResponse data = 2;
}
- userop_srv/proto/userfav.proto
syntax = "proto3";
import "google/protobuf/empty.proto";
option go_package = ".;proto";
service UserFav{
rpc GetFavList(UserFavRequest) returns(UserFavListResponse); //过滤收藏信息
rpc AddUserFav(UserFavRequest) returns(google.protobuf.Empty); //添加收藏
rpc DeleteUserFav(UserFavRequest) returns(google.protobuf.Empty); //删除收藏
rpc GetUserFavDetail(UserFavRequest) returns(google.protobuf.Empty); //查看用户是否已经收藏某件商品
}
message UserFavRequest{
int32 userId = 1;
int32 goodsId = 2;
}
message UserFavResponse{
int32 userId = 1;
int32 goodsId = 2;
}
message UserFavListResponse {
int32 total = 1;
repeated UserFavResponse data = 2;
}
- userop_srv/proto/gen_proto.bat:使用bat生成proto
protoc --go_out=. --go_opt=paths=import --go-grpc_out=. --go-grpc_opt=paths=import *.proto
- userop_srv/main.go:修改对应的proto注册及默认启动端口
func main() {
IP := flag.String("ip", "0.0.0.0", "ip地址")
Port := flag.Int("port", 50061, "端口号") // 这个修改为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.RegisterAddressServer(server, &proto.UnimplementedAddressServer{})
proto.RegisterMessageServer(server, &proto.UnimplementedMessageServer{})
proto.RegisterUserFavServer(server, &proto.UnimplementedUserFavServer{})
//省略。。。
5 - 启动userop_srv服务
二、service的handle实现
1 - handle_base
- userop_srv/handler/handle_base.go:因为我们拆分了3个handle,所以将公共部分UserOpServer结构和分页逻辑提取出来
package handler
import (
"gorm.io/gorm"
"nd/userop_srv/proto"
)
type UserOpServer struct {
proto.UnimplementedAddressServer
proto.UnimplementedUserFavServer
proto.UnimplementedMessageServer
}
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)
}
}
2 - handle_address
- userop_srv/handler/handle_address.go
package address
import (
"context"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"net/http"
"strconv"
"web_api/userop_web/api"
"web_api/userop_web/forms"
"web_api/userop_web/global"
"web_api/userop_web/models"
"web_api/userop_web/proto"
)
func List(ctx *gin.Context) {
request := &proto.AddressRequest{}
claims, _ := ctx.Get("claims")
currentUser := claims.(*models.CustomClaims)
if currentUser.AuthorityId != 2 {
userId, _ := ctx.Get("userId")
request.UserId = int32(userId.(uint))
}
rsp, err := global.AddressClient.GetAddressList(context.Background(), request)
if err != nil {
zap.S().Errorw("获取地址列表失败")
api.HandleGrpcErrorToHttp(err, ctx)
return
}
reMap := gin.H{
"total": rsp.Total,
}
result := make([]interface{}, 0)
for _, value := range rsp.Data {
reMap := make(map[string]interface{})
reMap["id"] = value.Id
reMap["user_id"] = value.UserId
reMap["province"] = value.Province
reMap["city"] = value.City
reMap["district"] = value.District
reMap["address"] = value.Address
reMap["signer_name"] = value.SignerName
reMap["signer_mobile"] = value.SignerMobile
result = append(result, reMap)
}
reMap["data"] = result
ctx.JSON(http.StatusOK, reMap)
}
func New(ctx *gin.Context) {
addressForm := forms.AddressForm{}
if err := ctx.ShouldBindJSON(&addressForm); err != nil {
api.HandleValidatorError(ctx, err)
return
}
userId, _ := ctx.Get("userId")
rsp, err := global.AddressClient.CreateAddress(context.Background(), &proto.AddressRequest{
UserId: int32(userId.(uint)),
Province: addressForm.Province,
City: addressForm.City,
District: addressForm.District,
Address: addressForm.Address,
SignerName: addressForm.SignerName,
SignerMobile: addressForm.SignerMobile,
})
if err != nil {
zap.S().Errorw("新建地址失败")
api.HandleGrpcErrorToHttp(err, ctx)
return
}
ctx.JSON(http.StatusOK, gin.H{
"id": rsp.Id,
})
}
func Delete(ctx *gin.Context) {
id := ctx.Param("id")
i, err := strconv.ParseInt(id, 10, 32)
if err != nil {
ctx.Status(http.StatusNotFound)
return
}
userId, _ := ctx.Get("userId")
_, err = global.AddressClient.DeleteAddress(context.Background(), &proto.AddressRequest{
Id: int32(i),
UserId: int32(userId.(uint)),
})
if err != nil {
zap.S().Errorw("删除地址失败")
api.HandleGrpcErrorToHttp(err, ctx)
return
}
ctx.JSON(http.StatusOK, gin.H{
"msg": "删除成功",
})
}
func Update(ctx *gin.Context) {
addressForm := forms.AddressForm{}
if err := ctx.ShouldBindJSON(&addressForm); err != nil {
api.HandleValidatorError(ctx, err)
return
}
id := ctx.Param("id")
i, err := strconv.ParseInt(id, 10, 32)
if err != nil {
ctx.Status(http.StatusNotFound)
return
}
userId, _ := ctx.Get("userId")
_, err = global.AddressClient.UpdateAddress(context.Background(), &proto.AddressRequest{
Id: int32(i),
UserId: int32(userId.(uint)),
Province: addressForm.Province,
City: addressForm.City,
District: addressForm.District,
Address: addressForm.Address,
SignerName: addressForm.SignerName,
SignerMobile: addressForm.SignerMobile,
})
if err != nil {
zap.S().Errorw("更新地址失败")
api.HandleGrpcErrorToHttp(err, ctx)
return
}
ctx.JSON(http.StatusOK, gin.H{})
}
3 - handle_message
- userop_srv/handler/handle_message.go
package handler
import (
"context"
"nd/userop_srv/global"
"nd/userop_srv/model"
"nd/userop_srv/proto"
)
func (*UserOpServer) MessageList(ctx context.Context, req *proto.MessageRequest) (*proto.MessageListResponse, error) {
var rsp proto.MessageListResponse
var messages []model.LeavingMessages
var messageList []*proto.MessageResponse
result := global.DB.Where(&model.LeavingMessages{User: req.UserId}).Find(&messages)
rsp.Total = int32(result.RowsAffected)
for _, message := range messages {
messageList = append(messageList, &proto.MessageResponse{
Id: message.ID,
UserId: message.User,
MessageType: message.MessageType,
Subject: message.Subject,
Message: message.Message,
File: message.File,
})
}
rsp.Data = messageList
return &rsp, nil
}
func (*UserOpServer) CreateMessage(ctx context.Context, req *proto.MessageRequest) (*proto.MessageResponse, error) {
var message model.LeavingMessages
message.User = req.UserId
message.MessageType = req.MessageType
message.Subject = req.Subject
message.Message = req.Message
message.File = req.File
global.DB.Save(&message)
return &proto.MessageResponse{Id: message.ID}, nil
}
4 - handle_userfav
- userop_srv/handler/handle_userfav.go
package handler
import (
"context"
"google.golang.org/protobuf/types/known/emptypb"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"nd/userop_srv/global"
"nd/userop_srv/model"
"nd/userop_srv/proto"
)
func (*UserOpServer) GetFavList(ctx context.Context, req *proto.UserFavRequest) (*proto.UserFavListResponse, error) {
var rsp proto.UserFavListResponse
var userFavs []model.UserFav
var userFavList []*proto.UserFavResponse
//查询用户的收藏记录
//查询某件商品被哪些用户收藏了
result := global.DB.Where(&model.UserFav{User: req.UserId, Goods: req.GoodsId}).Find(&userFavs)
rsp.Total = int32(result.RowsAffected)
for _, userFav := range userFavs {
userFavList = append(userFavList, &proto.UserFavResponse{
UserId: userFav.User,
GoodsId: userFav.Goods,
})
}
rsp.Data = userFavList
return &rsp, nil
}
func (*UserOpServer) AddUserFav(ctx context.Context, req *proto.UserFavRequest) (*emptypb.Empty, error) {
var userFav model.UserFav
userFav.User = req.UserId
userFav.Goods = req.GoodsId
global.DB.Save(&userFav)
return &emptypb.Empty{}, nil
}
func (*UserOpServer) DeleteUserFav(ctx context.Context, req *proto.UserFavRequest) (*emptypb.Empty, error) {
if result := global.DB.Unscoped().Where("goods=? and user=?", req.GoodsId, req.UserId).Delete(&model.UserFav{}); result.RowsAffected == 0 {
return nil, status.Errorf(codes.NotFound, "收藏记录不存在")
}
return &emptypb.Empty{}, nil
}
func (*UserOpServer) GetUserFavDetail(ctx context.Context, req *proto.UserFavRequest) (*emptypb.Empty, error) {
var userfav model.UserFav
if result := global.DB.Where("goods=? and user=?", req.GoodsId, req.UserId).Find(&userfav); result.RowsAffected == 0 {
return nil, status.Errorf(codes.NotFound, "收藏记录不存在")
}
return &emptypb.Empty{}, nil
}
5 - proto注册修改
- userop_srv/main.go:因为之前我们使用userop_srv/handler/handle_base.go统一定义了UserOpServer结构,所以我们在注册的时候可以将3个都注册成
&handler.UserOpServer{}
func main() {
IP := flag.String("ip", "0.0.0.0", "ip地址")
Port := flag.Int("port", 50061, "端口号") // 这个修改为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.RegisterAddressServer(server, &handler.UserOpServer{})
proto.RegisterMessageServer(server, &handler.UserOpServer{})
proto.RegisterUserFavServer(server, &handler.UserOpServer{})
//省略。。。
三、handle接口测试
- userop_srv/tests/userop/main.go:这里简单调用下3个查询接口,没有报错即可,其他的等我们在web服务中调用检查
package main
import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"nd/userop_srv/proto"
"nd/userop_srv/tests"
)
var userFavClient proto.UserFavClient
var messageClient proto.MessageClient
var addressClient proto.AddressClient
var conn *grpc.ClientConn
func TestAddressList() {
_, err := addressClient.GetAddressList(context.Background(), &proto.AddressRequest{
UserId: 1,
})
if err != nil {
panic(err)
}
}
func TestMessageList() {
_, err := messageClient.MessageList(context.Background(), &proto.MessageRequest{
UserId: 1,
})
if err != nil {
panic(err)
}
}
func TestUserFav() {
_, err := userFavClient.GetFavList(context.Background(), &proto.UserFavRequest{
UserId: 1,
})
if err != nil {
panic(err)
}
}
func Init() {
var err error
conn, err = grpc.Dial(tests.TargetAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic(err)
}
userFavClient = proto.NewUserFavClient(conn)
messageClient = proto.NewMessageClient(conn)
addressClient = proto.NewAddressClient(conn)
}
func main() {
Init()
TestAddressList()
TestMessageList()
TestUserFav()
conn.Close()
}
四、快速启动userop_web
1 - 快速生成项目
- userop_web:将order_web下除了tmp以外的文件直接拷贝到userop_web中;将order_web在选中文件夹中替换成userop_web
2 - api修改
- userop_web/api/address/api_address.go
package address
import (
"context"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"net/http"
"strconv"
"web_api/userop_web/api"
"web_api/userop_web/forms"
"web_api/userop_web/global"
"web_api/userop_web/models"
"web_api/userop_web/proto"
)
func List(ctx *gin.Context) {
request := &proto.AddressRequest{}
claims, _ := ctx.Get("claims")
currentUser := claims.(*models.CustomClaims)
if currentUser.AuthorityId != 2 {
userId, _ := ctx.Get("userId")
request.UserId = int32(userId.(uint))
}
rsp, err := global.AddressClient.GetAddressList(context.Background(), request)
if err != nil {
zap.S().Errorw("获取地址列表失败")
api.HandleGrpcErrorToHttp(err, ctx)
return
}
reMap := gin.H{
"total": rsp.Total,
}
result := make([]interface{}, 0)
for _, value := range rsp.Data {
reMap := make(map[string]interface{})
reMap["id"] = value.Id
reMap["user_id"] = value.UserId
reMap["province"] = value.Province
reMap["city"] = value.City
reMap["district"] = value.District
reMap["address"] = value.Address
reMap["signer_name"] = value.SignerName
reMap["signer_mobile"] = value.SignerMobile
result = append(result, reMap)
}
reMap["data"] = result
ctx.JSON(http.StatusOK, reMap)
}
func New(ctx *gin.Context) {
addressForm := forms.AddressForm{}
if err := ctx.ShouldBindJSON(&addressForm); err != nil {
api.HandleValidatorError(ctx, err)
return
}
userId, _ := ctx.Get("userId")
rsp, err := global.AddressClient.CreateAddress(context.Background(), &proto.AddressRequest{
UserId: int32(userId.(uint)),
Province: addressForm.Province,
City: addressForm.City,
District: addressForm.District,
Address: addressForm.Address,
SignerName: addressForm.SignerName,
SignerMobile: addressForm.SignerMobile,
})
if err != nil {
zap.S().Errorw("新建地址失败")
api.HandleGrpcErrorToHttp(err, ctx)
return
}
ctx.JSON(http.StatusOK, gin.H{
"id": rsp.Id,
})
}
func Delete(ctx *gin.Context) {
id := ctx.Param("id")
i, err := strconv.ParseInt(id, 10, 32)
if err != nil {
ctx.Status(http.StatusNotFound)
return
}
_, err = global.AddressClient.DeleteAddress(context.Background(), &proto.AddressRequest{Id: int32(i)})
if err != nil {
zap.S().Errorw("删除地址失败")
api.HandleGrpcErrorToHttp(err, ctx)
return
}
ctx.JSON(http.StatusOK, gin.H{
"msg": "删除成功",
})
}
func Update(ctx *gin.Context) {
addressForm := forms.AddressForm{}
if err := ctx.ShouldBindJSON(&addressForm); err != nil {
api.HandleValidatorError(ctx, err)
return
}
id := ctx.Param("id")
i, err := strconv.ParseInt(id, 10, 32)
if err != nil {
ctx.Status(http.StatusNotFound)
return
}
_, err = global.AddressClient.UpdateAddress(context.Background(), &proto.AddressRequest{
Id: int32(i),
Province: addressForm.Province,
City: addressForm.City,
District: addressForm.District,
Address: addressForm.Address,
SignerName: addressForm.SignerName,
SignerMobile: addressForm.SignerMobile,
})
if err != nil {
zap.S().Errorw("更新地址失败")
api.HandleGrpcErrorToHttp(err, ctx)
return
}
ctx.JSON(http.StatusOK, gin.H{})
}
- userop_web/api/message/api_message.go
package message
import (
"context"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"net/http"
"web_api/userop_web/api"
"web_api/userop_web/forms"
"web_api/userop_web/global"
"web_api/userop_web/models"
"web_api/userop_web/proto"
)
func List(ctx *gin.Context) {
request := &proto.MessageRequest{}
userId, _ := ctx.Get("userId")
claims, _ := ctx.Get("claims")
model := claims.(*models.CustomClaims)
if model.AuthorityId == 1 {
request.UserId = int32(userId.(uint))
}
rsp, err := global.MessageClient.MessageList(context.Background(), request)
if err != nil {
zap.S().Errorw("获取留言失败")
api.HandleGrpcErrorToHttp(err, ctx)
return
}
reMap := map[string]interface{}{
"total": rsp.Total,
}
result := make([]interface{}, 0)
for _, value := range rsp.Data {
reMap := make(map[string]interface{})
reMap["id"] = value.Id
reMap["user_id"] = value.UserId
reMap["type"] = value.MessageType
reMap["subject"] = value.Subject
reMap["message"] = value.Message
reMap["file"] = value.File
result = append(result, reMap)
}
reMap["data"] = result
ctx.JSON(http.StatusOK, reMap)
}
func New(ctx *gin.Context) {
userId, _ := ctx.Get("userId")
messageForm := forms.MessageForm{}
if err := ctx.ShouldBindJSON(&messageForm); err != nil {
api.HandleValidatorError(ctx, err)
return
}
rsp, err := global.MessageClient.CreateMessage(context.Background(), &proto.MessageRequest{
UserId: int32(userId.(uint)),
MessageType: messageForm.MessageType,
Subject: messageForm.Subject,
Message: messageForm.Message,
File: messageForm.File,
})
if err != nil {
zap.S().Errorw("添加留言失败")
api.HandleGrpcErrorToHttp(err, ctx)
return
}
ctx.JSON(http.StatusOK, gin.H{
"id": rsp.Id,
})
}
- userop_web/api/user_fav/api_user_fav.go
package user_fav
import (
"context"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"net/http"
"strconv"
"web_api/userop_web/api"
"web_api/userop_web/forms"
"web_api/userop_web/global"
"web_api/userop_web/proto"
)
func List(ctx *gin.Context) {
userId, _ := ctx.Get("userId")
userFavRsp, err := global.UserFavClient.GetFavList(context.Background(), &proto.UserFavRequest{
UserId: int32(userId.(uint)),
})
if err != nil {
zap.S().Errorw("获取收藏列表失败")
api.HandleGrpcErrorToHttp(err, ctx)
return
}
ids := make([]int32, 0)
for _, item := range userFavRsp.Data {
ids = append(ids, item.GoodsId)
}
if len(ids) == 0 {
ctx.JSON(http.StatusOK, gin.H{
"total": 0,
})
return
}
//请求商品服务
goods, err := global.GoodsSrvClient.BatchGetGoods(context.Background(), &proto.BatchGoodsIdInfo{
Id: ids,
})
if err != nil {
zap.S().Errorw("[List] 批量查询【商品列表】失败")
api.HandleGrpcErrorToHttp(err, ctx)
return
}
reMap := map[string]interface{}{
"total": userFavRsp.Total,
}
goodsList := make([]interface{}, 0)
for _, item := range userFavRsp.Data {
data := gin.H{
"id": item.GoodsId,
}
for _, good := range goods.Data {
if item.GoodsId == good.Id {
data["name"] = good.Name
data["shop_price"] = good.ShopPrice
}
}
goodsList = append(goodsList, data)
}
reMap["data"] = goodsList
ctx.JSON(http.StatusOK, reMap)
}
func New(ctx *gin.Context) {
userFavForm := forms.UserFavForm{}
if err := ctx.ShouldBindJSON(&userFavForm); err != nil {
api.HandleValidatorError(ctx, err)
return
}
//缺少一步, 这个时候应该去商品服务查询一下这个是否存在
userId, _ := ctx.Get("userId")
_, err := global.UserFavClient.AddUserFav(context.Background(), &proto.UserFavRequest{
UserId: int32(userId.(uint)),
GoodsId: userFavForm.GoodsId,
})
if err != nil {
zap.S().Errorw("添加收藏记录失败")
api.HandleGrpcErrorToHttp(err, ctx)
return
}
ctx.JSON(http.StatusOK, gin.H{})
}
func Delete(ctx *gin.Context) {
id := ctx.Param("id")
i, err := strconv.ParseInt(id, 10, 32)
if err != nil {
ctx.Status(http.StatusNotFound)
return
}
userId, _ := ctx.Get("userId")
_, err = global.UserFavClient.DeleteUserFav(context.Background(), &proto.UserFavRequest{
UserId: int32(userId.(uint)),
GoodsId: int32(i),
})
if err != nil {
zap.S().Errorw("删除收藏记录失败")
api.HandleGrpcErrorToHttp(err, ctx)
return
}
ctx.JSON(http.StatusOK, gin.H{
"msg": "删除成功",
})
}
func Detail(ctx *gin.Context) {
goodsId := ctx.Param("id")
goodsIdInt, err := strconv.ParseInt(goodsId, 10, 32)
if err != nil {
ctx.Status(http.StatusNotFound)
return
}
userId, _ := ctx.Get("userId")
_, err = global.UserFavClient.GetUserFavDetail(context.Background(), &proto.UserFavRequest{
UserId: int32(userId.(uint)),
GoodsId: int32(goodsIdInt),
})
if err != nil {
zap.S().Errorw("查询收藏状态失败")
api.HandleGrpcErrorToHttp(err, ctx)
return
}
ctx.Status(http.StatusOK)
}
3 - config修改
- userop_web/config/config.go
package config
type SrvConfig struct {
Name string `mapstructure:"name" json:"name"`
}
type JWTConfig struct {
SigningKey string `mapstructure:"key" json:"key"`
}
type ConsulConfig struct {
Host string `mapstructure:"host" json:"host"`
Port int `mapstructure:"port" json:"port"`
}
type ServerConfig struct {
Name string `mapstructure:"name" json:"name"`
Host string `mapstructure:"host" json:"host"`
Tags []string `mapstructure:"tags" json:"tags"`
Port int `mapstructure:"port" json:"port"`
GoodsSrvInfo SrvConfig `mapstructure:"goods_srv" json:"goods_srv"`
UserOpSrvInfo SrvConfig `mapstructure:"userop_srv" json:"userop_srv"`
JWTInfo JWTConfig `mapstructure:"jwt" json:"jwt"`
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"`
}
4 - form表单修改
- userop_web/forms/form_address.go
package forms
type AddressForm struct {
Province string `form:"province" json:"province" binding:"required"`
City string `form:"city" json:"city" binding:"required"`
District string `form:"district" json:"district" binding:"required"`
Address string `form:"address" json:"address" binding:"required"`
SignerName string `form:"signer_name" json:"signer_name" binding:"required"`
SignerMobile string `form:"signer_mobile" json:"signer_mobile" binding:"required"`
}
- userop_web/forms/form_message.go
package forms
type MessageForm struct {
MessageType int32 `form:"type" json:"type" binding:"required,oneof=1 2 3 4 5"`
Subject string `form:"subject" json:"subject" binding:"required"`
Message string `form:"message" json:"message" binding:"required"`
File string `form:"file" json:"file" binding:"required"`
}
- userop_web/forms/form_user_fav.go
package forms
type UserFavForm struct {
GoodsId int32 `form:"goods" json:"goods" binding:"required"`
}
5 - 从srv拷贝proto文件
6 - global修改
- userop_web/global/global.go
package global
import (
ut "github.com/go-playground/universal-translator"
"web_api/userop_web/config"
"web_api/userop_web/proto"
)
var (
Trans ut.Translator
ServerConfig *config.ServerConfig = &config.ServerConfig{}
NacosConfig *config.NacosConfig = &config.NacosConfig{}
GoodsSrvClient proto.GoodsClient
MessageClient proto.MessageClient
AddressClient proto.AddressClient
UserFavClient proto.UserFavClient
)
7 - router修改
- userop_web/router/router_address.go
package router
import (
"github.com/gin-gonic/gin"
"web_api/userop_web/api/address"
"web_api/userop_web/middlewares"
)
func InitAddressRouter(Router *gin.RouterGroup) {
//AddressRouter := Router.Group("address").Use(middlewares.JWTAuth())
AddressRouter := Router.Group("address").Use(middlewares.SetUserId())
{
AddressRouter.GET("", address.List)
AddressRouter.DELETE("/:id", address.Delete)
AddressRouter.POST("", address.New)
AddressRouter.PUT("/:id", address.Update)
}
}
- userop_web/router/router_message.go
package router
import (
"github.com/gin-gonic/gin"
"web_api/userop_web/api/message"
"web_api/userop_web/middlewares"
)
func InitMessageRouter(Router *gin.RouterGroup) {
//MessageRouter := Router.Group("message").Use(middlewares.JWTAuth())
MessageRouter := Router.Group("message").Use(middlewares.SetUserId())
{
MessageRouter.GET("", message.List) // 轮播图列表页
MessageRouter.POST("", message.New) //新建轮播图
}
}
- userop_web/router/router_user_fav.go
package router
import (
"github.com/gin-gonic/gin"
"web_api/userop_web/api/user_fav"
"web_api/userop_web/middlewares"
)
func InitUserFavRouter(Router *gin.RouterGroup) {
//UserFavRouter := Router.Group("userfavs").Use(middlewares.JWTAuth())
UserFavRouter := Router.Group("userfavs").Use(middlewares.SetUserId())
{
UserFavRouter.DELETE("/:id", user_fav.Delete) // 删除收藏记录
UserFavRouter.GET("/:id", user_fav.Detail) // 获取收藏记录
UserFavRouter.POST("", user_fav.New) //新建收藏记录
UserFavRouter.GET("", user_fav.List) //获取当前用户的收藏
}
}
- userop_web/initialize/init_router.go:初始化router修改
package initialize
import (
"github.com/gin-gonic/gin"
"web_api/userop_web/middlewares"
"web_api/userop_web/router"
)
func Routers() *gin.Engine {
Router := gin.Default()
//配置跨域
Router.Use(middlewares.Cors())
ApiGroup := Router.Group("/up/v1")
router.InitAddressRouter(ApiGroup)
router.InitMessageRouter(ApiGroup)
router.InitUserFavRouter(ApiGroup)
return Router
}
8 - 初始化连接修改
- userop_web/initialize/init_srv_conn.go
package initialize
import (
"fmt"
_ "github.com/mbobakov/grpc-consul-resolver" // It's important
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"web_api/userop_web/global"
"web_api/userop_web/proto"
)
func InitSrvConn() {
consulInfo := global.ServerConfig.ConsulInfo
goodsConn, err := grpc.Dial(
fmt.Sprintf("consul://%s:%d/%s?wait=14s", consulInfo.Host, consulInfo.Port, global.ServerConfig.GoodsSrvInfo.Name),
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`),
)
if err != nil {
zap.S().Fatal("[InitSrvConn] 连接 【商品服务失败】")
}
global.GoodsSrvClient = proto.NewGoodsClient(goodsConn)
userOpConn, err := grpc.Dial(
fmt.Sprintf("consul://%s:%d/%s?wait=14s", consulInfo.Host, consulInfo.Port, global.ServerConfig.UserOpSrvInfo.Name),
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`),
)
if err != nil {
zap.S().Fatal("[InitSrvConn] 连接 【用户操作服务失败】")
}
global.UserFavClient = proto.NewUserFavClient(userOpConn)
global.MessageClient = proto.NewMessageClient(userOpConn)
global.AddressClient = proto.NewAddressClient(userOpConn)
}
9 - nacos配置与yaml修改
- 克隆配置:注意需要添加userop_srv服务;端口修改为8085
{
"host": "192.168.78.1",
"name": "userop_web",
"port": 8085,
"tags": ["mxshop","imooc","bobby","userop","web"],
"goods_srv": {
"name": "goods_srv"
},
"userop_srv": {
"name": "userop_srv"
},
"jwt": {
"key": "VYLDYq3&hGWjWqF$K1ih"
},
"consul": {
"host": "192.168.78.131",
"port": 8500
}
}
- yaml修改空间id
host: '192.168.124.51'
port: 8848
namespace: '167f44f6-8d47-4b13-a01a-e2a570dabaab'
user: 'nacos'
password: 'nacos'
dataid: 'userop_web.json'
group: 'zsz'
五、YApi测试userop_web
测试需要启动goods_srv服务、userop_srv服务、userop_web服务
1 - 收藏YApi测试
- 查询用户收藏列表
- 查询用户是否收藏某商品
- 删除某件商品的收藏
2 - 地址YApi测试
- 添加地址
- 地址列表
- 更新地址
- 删除地址
3 - 留言YApi测试
- 添加留言
- 留言列表
六、完整源码
- 完整源码下载:mxshop_srvsV10.0rar
- 源码说明:(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配置集导入文件
- other_import/nacos_config_export_inventory.zip:nacos的inventory的配置导入文件
- other_import/nacos_config_export_orders.zip:nacos的orders的配置导入文件
- other_import/nacos_config_export_userop.zip:nacos的userop的配置导入文件