栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Go语言

【手机商城】Go-Micro 微服务全栈项目案例

Go语言 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【手机商城】Go-Micro 微服务全栈项目案例

承接Web、小程序等全栈开发项目、毕业设计等(附送HTTPS建站、国内域名解析备案指导)

0. 功能展示及项目结构 0.1 功能展示

0.2 项目结构
logs
proto
	- goods.ext.proto
src
	- api-srv/main.go
	- goods-srv
		- dao
			- db.go
			- goods.go
		- handler/goods.ext.go
		main.go
	- share
		- config/config.go
		- errors/phone.go
		- log/log.go
		- path/path.go
		- pb
		const.go 
go.mod
1. 建库建表、启动consul
CREATE DATABASE `mtbapp`;
CREATE TABLE `goods_info` ( 
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增id',
  `name` varchar(255) NOT NULL DEFAULT '' COMMENT '商品名',
  `price` varchar(255) NOT NULL DEFAULT '' COMMENT '价格',
  `brand` varchar(255) NOT NULL DEFAULT '' COMMENT '品牌',
  `desc` varchar(1024) NOT NULL DEFAULT '' COMMENT '描述',
  primary key(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品表';
docker run -p 8300:8300 -p 8301:8301 -p 8301:8301/udp -p 8302:8302/udp -p 8302:8302 -p 8400:8400 -p 8500:8500 -p 53:53/udp consul
2. proto定义
syntax = "proto3";
package pb;
service GoodsServiceExt {
    // 根据商品ID获取商品详情
    rpc GetGoodsInfoByGid(GetGoodsInfoReq) returns(GetGoodsInfoRsp) {}
    // 根据品牌获取手机列表
    rpc GetGoodsInfoByBid(GetBrandGoodsReq) returns(GetBrandGoodsRsp) {}
}
message GoodsInfo {
    int64 id = 1;     // 商品id
    string name = 2;  // 商品名
    string price = 3; // 价格
    string brand = 4; // 品牌
    string desc = 5;  // 描述
}
message GetGoodsInfoReq {
    int64 GoodsId = 1; // 商品id
}
message GetGoodsInfoRsp {
    GoodsInfo goodsInfo = 1;
}
message GetBrandGoodsReq {
    int64 brandId = 1; // 品牌id
}
message GetBrandGoodsRsp {
    repeated GoodsInfo goodsList = 1;
}
3.生成模型和接口
protoc --proto_path=$GOPATH/src:. --go_out=../src/share/pb/  goods.ext.proto
protoc --proto_path=$GOPATH/src:. --micro_out=../src/share/pb/  goods.ext.proto
4. 商品服务(增删改查等) [ 可以依据此继续实现用户、订单、评论等服务 ] 4.1 模型层

dao/db.go

package dao
import (
	_ "github.com/go-sql-driver/mysql"
	"github.com/jmoiron/sqlx"
)
var db *sqlx.DB
func Init(mysqlDSN string) {
	db = sqlx.MustConnect("mysql", mysqlDSN)
	db.SetMaxIdleConns(1)
	db.SetMaxOpenConns(3)
}

dao/goods.go

package dao
import (
	"database/sql"
	"github.com/jstang9527/phone/src/share"
)
type Goods struct {
	Id    int64  `json:"id" db:"id"`
	Name  string `json:"name" db:"name"`
	Price string `json:"price" db:"price"`
	Brand string `json:"brand" db:"brand"`
	Desc  string `json:"desc" db:"desc"`
}
func SelectGoodsByGid(goodsId int64) (*Goods, error) {
	var goods Goods
	err := db.Get(&goods, "SELECT `id`,`name`,`price`,`brand`,`desc` FROM `goods_info` WHERe `id` = ?", goodsId)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	return &goods, nil
}
func SelectGoodsByBid(brand_id int64) ([]*Goods, error) {
	var goodsList = []*Goods{}
	brand := share.SwitchBrandNameByBid(brand_id)
	err := db.Select(&goodsList, "SELECT `id`,`name`,`price`,`brand`,`desc` FROM `goods_info` WHERe `brand` = ?", brand)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	return goodsList, err
}
4.2 控制器
package handler
import (
	"github.com/jstang9527/phone/src/goods-srv/dao"
	"github.com/jstang9527/phone/src/share/errors"
	"github.com/jstang9527/phone/src/share/log"
	"github.com/jstang9527/phone/src/share/pb"
	"go.uber.org/zap"
)

type GoodsServiceExtHandler struct {
	logger *zap.Logger
}
func NewPhoneServiceExtHandler() *GoodsServiceExtHandler {
	return &GoodsServiceExtHandler{
		logger: log.Instance(),
	}
}
func (c *GoodsServiceExtHandler) GetGoodsInfoByGid(ctx context.Context, req *pb.GetGoodsInfoReq, res *pb.GetGoodsInfoRsp) error {
	c.logger.Debug("debug", zap.Any("GoodsId", req.GoodsId))
	info, err := dao.SelectGoodsByGid(req.GoodsId)
	if err != nil {
		c.logger.Error("error", zap.Error(err))
		return errors.ErrorPhoneFailed
	}
	out := &pb.GoodsInfo{
		Id:    info.Id,
		Name:  info.Name,
		Price: info.Price,
		Brand: info.Brand,
		Desc:  info.Desc,
	}
	res.GoodsInfo = out
	return nil
}
func (c *GoodsServiceExtHandler) GetGoodsInfoByBid(ctx context.Context, req *pb.GetBrandGoodsReq, res *pb.GetBrandGoodsRsp) error {
	c.logger.Debug("debug", zap.Any("BrandId", req.BrandId))
	array, err := dao.SelectGoodsByBid(req.BrandId)
	if err != nil {
		c.logger.Error("error", zap.Error(err))
		return errors.ErrorPhoneFailed
	}
	out := make([]*pb.GoodsInfo, 0, len(array))
	for _, item := range array {
		out = append(out, &pb.GoodsInfo{
			Id:    item.Id,
			Name:  item.Name,
			Price: item.Price,
			Brand: item.Brand,
			Desc:  item.Desc,
		})
	}
	res.GoodsList = out
	return nil
}
4.3 服务注册
package main
import (
	"github.com/jstang9527/phone/src/goods-srv/dao"
	"github.com/jstang9527/phone/src/goods-srv/handler"
	"github.com/jstang9527/phone/src/share/config"
	"github.com/jstang9527/phone/src/share/log"
	"github.com/jstang9527/phone/src/share/pb"
	"github.com/micro/cli"
	"github.com/micro/go-micro"
	"github.com/micro/go-micro/registry"
	"github.com/micro/go-micro/server"
	"github.com/micro/go-plugins/registry/consul"
	"go.uber.org/zap"
)
func main() {
	log.Init("phone", false)
	consulReg := consul.NewRegistry(registry.Addrs(config.ConsulAddr))
	logger := log.Instance()
	service := micro.NewService(
		micro.Name(config.Namespace+config.ServiceNameGoods),
		micro.Version("latest"),
		micro.Registry(consulReg),
	)
	// 定义Service动作操作
	service.Init(
		micro.Action(func(c *cli.Context) {
			logger.Info("Info", zap.Any("goods-srv", "goods-srv is start ..."))
			dao.Init(config.MysqlDSN)
			pb.RegisterGoodsServiceExtHandler(service.Server(), handler.NewPhoneServiceExtHandler(), server.InternalHandler(true))
		}),
		micro.AfterStop(func() error {
			logger.Info("Info", zap.Any("goods-srv", "goods-srv is stop ..."))
			return nil
		}),
		micro.AfterStart(func() error {
			logger.Info("Info", zap.Any("goods-srv", "goods-srv is AfterStart ..."))
			return nil
		}),
	)
	//启动service
	if err := service.Run(); err != nil {
		logger.Panic("goods-srv服务启动失败 ...")
	}
}
5. API网关、及调用测试

api-srv/main.go

package main

import (
	"github.com/jstang9527/phone/src/share/config"
	"github.com/jstang9527/phone/src/share/path"
	"github.com/micro/go-micro"
	"github.com/micro/go-micro/client"
	"github.com/micro/go-micro/registry"
	"github.com/micro/go-plugins/registry/consul"
)

var consulReg = consul.NewRegistry(registry.Addrs(config.ConsulAddr))

func main() {
	consulService := micro.NewService(micro.Registry(consulReg))
	consulService.Init()
	gateway := &Gateway{c: consulService.Client()}

	mux := http.NewServeMux()
	mux.HandleFunc("/", gateway.handleJSONRPC)
	log.Println("Start server at ::8888")
	log.Fatal(http.ListenAndServe(":8888", mux))
}

type Gateway struct {
	c client.Client
}

// 处理具体的rpc请求
func (g *Gateway) handleJSONRPC(w http.ResponseWriter, r *http.Request) {
	// 处理请求路径, 得到具体服务和方法、将url转换为service和method
	service, method := path.PathToReceiver(config.Namespace, r.URL.Path)
	log.Println("service:" + service, "method:" + method)

	// 读取请求体
	br, _ := ioutil.ReadAll(r.Body)
	// 封装request
	request := json.RawMessage(br)
	// 调用服务
	var response json.RawMessage
	req := g.c.NewRequest(service, method, &request, client.WithContentType("application/json"))
	err := g.c.Call(path.RequestToContext(r), req, &response)
	if err != nil {
		log.Println(err)
		return
	}
	// 编码json
	b, _ := response.MarshalJSON()
	w.Header().Set("Content-Length", strconv.Itoa(len(b)))
	if _, err = w.Write(b); err != nil {
		log.Println(err)
	}
}


6. 附件: share通用包代码(非重点)

6.1 config/config.go

package config
const (
	ConsulAddr   = "127.0.0.1:8500"
	MysqlDSN     = "root:123456@(localhost:3306)/mtbapp"
	Namespace    = "com.mtbapp."
	LogPath      = "/root/go/src/github.com/jstang9527/phone/logs" // "D:\go_work\src\my-micro\logdata"
	ServiceNameUser  = "user"
	ServiceNameGoods = "goods"
	ServiceNameOrder = "order"
)

6.2 errors/phone.go

package errors
import (
	"github.com/jstang9527/phone/src/share/config"
	"github.com/micro/go-micro/errors"
)
const errorCodePhoneSuccess = 200
var (
	ErrorPhoneFailed = errors.New(config.ServiceNameUser, "操作异常", errorCodePhoneSuccess)
	ErrorPhoneNotFound = errors.New(config.ServiceNameUser, "找不到记录", errorCodePhoneSuccess)
)

6.3 log/log.go

package log
import (
	"bytes"
	"log"
	"os"
	"path"
	"time"

	"github.com/jstang9527/phone/src/share/config"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
)

var instance *zap.Logger
// Instance 唯一实例
func Instance() *zap.Logger {
	return instance
}
// Init 初始化,srvName 生成的日志文件夹名字
func Init(srvName string, Pro bool) *zap.Logger {
	instance = NewLogger(srvName, Pro)
	return instance
}
// NewLogger 新建日志
func NewLogger(srvName string, Pro bool) *zap.Logger {
	directory := config.LogPath //"D:\Tools\Go_Work\src\my-micro\"
	if len(directory) == 0 {
		directory = path.Join("..", "logs", srvName)
	} else {
		directory = path.Join(directory, srvName)
	}
	writers := []zapcore.WriteSyncer{newRollingFile(directory)}
	writers = append(writers, os.Stdout)
	logger, _ := newZapLogger(Pro, zapcore.NewMultiWriteSyncer(writers...))
	zap.RedirectStdLog(logger)
	return logger
}
func newRollingFile(directory string) zapcore.WriteSyncer {
	if err := os.MkdirAll(directory, 0766); err != nil {
		log.Println("failed create log directory:", directory, ":", err)
		return nil
	}
	return newLumberjackWriteSyncer(&lumberjack.Logger{
		Filename:  path.Join(directory, "output.log"),
		MaxSize:   100, //megabytes
		MaxAge:    7,   //days
		LocalTime: true,
		Compress:  false,
	})
}
func newZapLogger(isProduction bool, output zapcore.WriteSyncer) (*zap.Logger, *zap.AtomicLevel) {
	encCfg := zapcore.EncoderConfig{
		TimeKey:        "@timestamp",
		LevelKey:       "level",
		NameKey:        "logger",
		CallerKey:      "caller",
		MessageKey:     "msg",
		StacktraceKey:  "stacktrace",
		EncodeCaller:   zapcore.ShortCallerEncoder,
		EncodeDuration: zapcore.NanosDurationEncoder,
		EncodeTime: func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
			enc.AppendString(t.Format("2006-01-02 15:04:05.000"))
		},
	}

	var encoder zapcore.Encoder
	dyn := zap.NewAtomicLevel()
	if isProduction {
		dyn.SetLevel(zap.InfoLevel)
		encCfg.EncodeLevel = zapcore.LowercaseLevelEncoder
		encoder = zapcore.NewConsoleEncoder(encCfg)
	} else {
		dyn.SetLevel(zap.DebugLevel)
		encCfg.EncodeLevel = zapcore.LowercaseColorLevelEncoder
		encoder = zapcore.NewConsoleEncoder(encCfg)
	}

	return zap.New(zapcore.NewCore(encoder, output, dyn), zap.AddCaller()), &dyn
}
type lumberjackWriteSyncer struct {
	*lumberjack.Logger
	buf       *bytes.Buffer
	logChan   chan []byte
	closeChan chan interface{}
	maxSize   int
}
func newLumberjackWriteSyncer(l *lumberjack.Logger) *lumberjackWriteSyncer {
	ws := &lumberjackWriteSyncer{
		Logger:    l,
		buf:       bytes.NewBuffer([]byte{}),
		logChan:   make(chan []byte, 5000),
		closeChan: make(chan interface{}),
		maxSize:   1024,
	}
	go ws.run()
	return ws
}
func (l *lumberjackWriteSyncer) run() {
	ticker := time.NewTicker(1 * time.Second)
	for {
		select {
		case <-ticker.C:
			if l.buf.Len() > 0 {
				l.sync()
			}
		case bs := <-l.logChan:
			_, err := l.buf.Write(bs)
			if err != nil {
				continue
			}
			if l.buf.Len() > l.maxSize {
				l.sync()
			}
		case <-l.closeChan:
			l.sync()
			return
		}
	}
}
func (l *lumberjackWriteSyncer) Stop() {
	close(l.closeChan)
}
func (l *lumberjackWriteSyncer) Write(bs []byte) (int, error) {
	b := make([]byte, len(bs))
	for i, c := range bs {
		b[i] = c
	}
	l.logChan <- b
	return 0, nil
}
func (l *lumberjackWriteSyncer) Sync() error {
	return nil
}
func (l *lumberjackWriteSyncer) sync() error {
	defer l.buf.Reset()
	_, err := l.Logger.Write(l.buf.Bytes())
	if err != nil {
		return err
	}
	return nil
}

6.4 path/path.go

package path
import (
	"net/http"
	"path"
	"regexp"
	"strings"

	"github.com/micro/go-micro/metadata"
	"golang.org/x/net/context"
)
var versionRe = regexp.MustCompilePOSIX("^v[0-9]+$")
// Translates /foo/bar/zool into api service go.micro.api.foo method Bar.Zool
// Translates /foo/bar into api service go.micro.api.foo method Foo.Bar
func PathToReceiver(ns, p string) (string, string) {
	// 路径处理,例如 http:xxx,会处理为http://xxx
	p = path.Clean(p)
	p = strings.TrimPrefix(p, "/")
	parts := strings.Split(p, "/")

	// If we've got two or less parts
	// Use first part as service
	// Use all parts as method
	if len(parts) <= 2 {
		service := ns + strings.Join(parts[:len(parts)-1], ".")
		method := strings.Title(strings.Join(parts, "."))
		return service, method
	}

	// Treat /v[0-9]+ as versioning where we have 3 parts
	// /v1/foo/bar => service: v1.foo method: Foo.bar
	if len(parts) == 3 && versionRe.Match([]byte(parts[0])) {
		service := ns + strings.Join(parts[:len(parts)-1], ".")
		method := strings.Title(strings.Join(parts[len(parts)-2:], "."))
		return service, method
	}

	// Service is everything minus last two parts
	// Method is the last two parts
	service := ns + strings.Join(parts[:len(parts)-2], ".")
	method := strings.Title(strings.Join(parts[len(parts)-2:], "."))
	return service, method
}
func RequestToContext(r *http.Request) context.Context {
	ctx := context.Background()
	md := make(metadata.Metadata)
	for k, v := range r.Header {
		md[k] = strings.Join(v, ",")
	}
	return metadata.NewContext(ctx, md)
}

6.5 const.go

package share
func SwitchBrandNameByBid(brandId int64) string {
	switch brandId {
	case 1:
		return "HUAWEI"
	case 2:
		return "Apple"
	default:
		return "Unknow"
	}
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/944682.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号