0. 功能展示及项目结构 0.1 功能展示 0.2 项目结构承接Web、小程序等全栈开发项目、毕业设计等(附送HTTPS建站、国内域名解析备案指导)
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.mod1. 建库建表、启动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 consul2. 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.proto4. 商品服务(增删改查等) [ 可以依据此继续实现用户、订单、评论等服务 ] 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"
}
}



