当前位置: 代码迷 >> 综合 >> 【手机商城】Go-Micro 微服务全栈项目案例
  详细解决方案

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

热度:66   发布时间:2023-12-29 04:59:58.0

承接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.gomain.go- share- config/config.go- errors/phone.go- log/log.go- path/path.go- pbconst.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;     // 商品idstring 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 Goodserr := 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 = outreturn 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 = outreturn 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}),)//启动serviceif err := service.Run(); err != nil {
    logger.Panic("goods-srv服务启动失败 ...")}
}

5. API网关、及调用测试

api-srv/main.go

package mainimport ("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和methodservice, method := path.PathToReceiver(config.Namespace, r.URL.Path)log.Println("service:" + service, "method:" + method)// 读取请求体br, _ := ioutil.ReadAll(r.Body)// 封装requestrequest := json.RawMessage(br)// 调用服务var response json.RawMessagereq := 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}// 编码jsonb, _ := 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, //megabytesMaxAge:    7,   //daysLocalTime: 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.Encoderdyn := zap.NewAtomicLevel()if isProduction {
    dyn.SetLevel(zap.InfoLevel)encCfg.EncodeLevel = zapcore.LowercaseLevelEncoderencoder = zapcore.NewConsoleEncoder(encCfg)} else {
    dyn.SetLevel(zap.DebugLevel)encCfg.EncodeLevel = zapcore.LowercaseColorLevelEncoderencoder = zapcore.NewConsoleEncoder(encCfg)}return zap.New(zapcore.NewCore(encoder, output, dyn), zap.AddCaller()), &dyn
}
type lumberjackWriteSyncer struct {
    *lumberjack.Loggerbuf       *bytes.BufferlogChan   chan []bytecloseChan 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 <- breturn 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://xxxp = 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 methodif 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.barif 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 partsservice := 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"}
}
  相关解决方案