【Go】Go-Iris框架实战之电商秒杀系统①后端开发

从课程内容来说,对于初学者来说 十分我给7分。。。
加分的是 秒杀这种场景使用的技术,
但想入手iris框架 还是看看其他的课吧~

文章目录

    • 课程内容
    • 2-1 需求分析
    • 2-2 原型需求分析
    • 2-3 系统架构设计
    • 3-3 RabbitMQ核心概念
  • 第4章 环境搭建之Iris
    • MVC
    • 第5章 后台管理功能开发之商品管理功能开发
    • 5-2 商品模型开发
    • 5-3 商品repository开发---数据连接
      • 数据库查找utils
      • db数据库连接
    • 5-5 商品repository开发---插入
    • 5-6 商品repository开发---删除&更新
    • 5-7 商品repository开发---查询单条记录
      • 查找全部
    • 5-9 主函数main.go功能讲解
    • 5-10 商品管理功能Controller&View开发
      • 获取所有的商品
      • 更新所有商品
  • 商品、用户、订单
    • 8-3 商品详情展示页面---访问权限设置(middleware)
    • 8-5 商品数据控制

课程内容

高并架构图
【Go】Go-Iris框架实战之电商秒杀系统①后端开发_第1张图片

2-1 需求分析

2-2 原型需求分析

2-3 系统架构设计

【Go】Go-Iris框架实战之电商秒杀系统①后端开发_第2张图片
3-1 RabitMq 环境安装

在window启动RabitMq 启动出现如下错误,请看这篇博客

DIAGNOSTICS
===========

attempted to contact: [rabbit@MVUIBUDNQI33TUM]

rabbit@MVUIBUDNQI33TUM:
  * connected to epmd (port 4369) on MVUIBUDNQI33TUM
  * epmd reports node 'rabbit' uses port 25672 for inter-node and CLI tool traffic
  * TCP connection succeeded but Erlang distribution failed

  * Authentication failed (rejected by the remote node), please check the Erlang cookie

启动成功
【Go】Go-Iris框架实战之电商秒杀系统①后端开发_第3张图片
【Go】Go-Iris框架实战之电商秒杀系统①后端开发_第4张图片
3-2 RabbitMQ常用命令及管理界面

1、以应用方式启动

rabbitmq-server -detached 后台启动

Rabbitmq-server 直接启动,如果你关闭窗口或者需要在改窗口使用其他命令时应用就会停止

 关闭:rabbitmqctl stop

2、以服务方式启动(安装完之后在任务管理器中服务一栏能看到RabbtiMq)

rabbitmq-service install 安装服务

rabbitmq-service start 开始服务

Rabbitmq-service stop  停止服务

Rabbitmq-service enable 使服务有效

Rabbitmq-service disable 使服务无效

rabbitmq-service help 帮助

当rabbitmq-service install之后默认服务是enable的,如果这时设置服务为disable的话,rabbitmq-service start就会报错。

当rabbitmq-service start正常启动服务之后,使用disable是没有效果的

  关闭:rabbitmqctl stop

3、Rabbitmq 管理插件启动,可视化界面

rabbitmq-plugins enable rabbitmq_management 启动

rabbitmq-plugins disable rabbitmq_management 关闭

【Go】Go-Iris框架实战之电商秒杀系统①后端开发_第5张图片

3-3 RabbitMQ核心概念

  • vittual host 区分队列,隔离账号
    Server: 又称Broker, 接受客户端的连接,实现AMQP实体服务,这里指RabbitMQ 服务器

Connection: 连接,应用程序与Broker的网络连接。

**Channel: **网络信道,几乎所有的操作都在 Channel 中进行,Channel是进行消息读写的通道。客户端可建立多个Channel:,每个Channel代表一个会话任务。

**Virtual host: **虚似地址,用于迸行逻辑隔离,是最上层的消息路由。一个 Virtual Host 里面可以有若干个 Exchange和 Queue ,同一个 VirtualHost 里面不能有相同名称的 Exchange 或 Queue。权限控制的最小粒度是Virtual Host。

Binding: Exchange 和 Queue 之间的虚拟连接,binding 中可以包含 routing key。

Routing key: 一 个路由规则,虚拟机可用它来确定如何路由一个特定消息,即交换机绑定到 Queue 的键。

Queue: 也称为Message Queue,消息队列,保存消息并将它们转发给消费者。

建立账号
【Go】Go-Iris框架实战之电商秒杀系统①后端开发_第6张图片
3-4 RabbitMQ工作模式—Simple模式(上)
https://www.cnblogs.com/haixiang/p/10853467.html
3-5 RabbitMQ工作模式—Simple模式(中)

package main

import (
	"fmt"
	"log"

	"github.com/streadway/amqp"
)

const MQURL = "amqp://immocuser:[email protected]:5672/imoooc"

type RabbitMQ struct {
	conn    *amqp.Connection
	channel *amqp.Channel

	QueueName string
	Exchange  string
	Key       string
	Mqurl     string
}

// 创建RabbitMQ 结构体实例
func NewRabbitMQ(queueName string, exchange string, key string) *RabbitMQ {

	rabbitmq := &RabbitMQ{QueueName: queueName, Exchange: exchange, Key: key, Mqurl: MQURL}

	// 创建简单模式的RabbitMQ实例

	var err error
	// 连接 rabbitmq
	rabbitmq.conn, err = amqp.Dial(rabbitmq.Mqurl)
	rabbitmq.failOnErr(err, "创建连接错误")

	rabbitmq.channel, err = rabbitmq.conn.Channel()
	rabbitmq.failOnErr(err, "获取channel 失败")
	return rabbitmq

}

// close RabbitMQ channel and connection
func (r *RabbitMQ) Destory() {
	r.channel.Close()
	r.conn.Close()
}

// err handler
func (r *RabbitMQ) failOnErr(err error, message string) {
	if err != nil {
		log.Fatalf("%s%:s", message, err)
		panic(fmt.Sprintf("%s:%s", message, err))
	}
}

//  创建简单模式的RabbitMQ实例
func NewRabbitSimple(queueName string) *RabbitMQ {
	return NewRabbitMQ(queueName, "", "")

}

// 简单模式
func (r *RabbitMQ) PublishSimple(message string) {
	// 1. 申请队列, 不存在则自动创建, 存在则跳过
	//    作用: 保证消息发送队列中
	_, err := r.channel.QueueDeclare(
		r.QueueName,
		// 是否持久化
		false,
		//是否自动删除
		false,
		// 是否排他
		false,
		// 是否阻塞
		false,
		nil,
	)
	if err != nil {
		fmt.Println(err)
	}

	// 发送消息到队列中
	r.channel.Publish(
		// 交换机
		r.Exchange,
		r.QueueName,
		// true会根据Exchange类型如果无法找到条件的队列,会把消息返回
		false,
		// true Exchange发送消息到队列上,没有发现绑定消费者,会返回
		false,
		// 发送信息格式
		amqp.Publishing{
			ContentType:"text/plain",
			Body:[]byte(message),

		}
	)
}

func main() {
	fmt.Print("6666")
	fmt.Println("")
	fmt.Println("666")
}

自闭中。。。后续再写这个项目吧。。。

3-6 RabbitMQ工作模式—Simple模式(下)

3-7 RabbitMQ工作模式—Work模式

3-8 RabbitMQ工作模式—Publish模式试看

3-9 RabbitMQ工作模式—Routing模式

3-10 RabbitMQ工作模式—Topic模式

3-11 【扩展资料】CentOS7 之基础设置及常见操作命令

3-12 【阶段总结】环境搭建之初识RabbitMQ

3-13 【勤于思考,夯实学习成果】阶段练习题

第4章 环境搭建之Iris

MVC

【Go】Go-Iris框架实战之电商秒杀系统①后端开发_第7张图片

第5章 后台管理功能开发之商品管理功能开发

5-2 商品模型开发

package datamodels

// 商品
type Product struct {
	Id           int64  `json:"Id" sql:"id" imooc:"Id"`
	ProductName  string `json:"ProductName" sql:"productName" imooc:"ProductName"`
	ProductNum   int64  `json:"ProductNum" sql:"productNum" imooc:"ProductNum"`
	ProductImage string `json:"ProductImage" sql:"productImage" imooc:"ProductImage"`
	ProductUrl   string `json:"ProductUrl" sql:"productUrl" imooc:"ProductUrl"`
}

5-3 商品repository开发—数据连接

数据库查找utils

package common

import (
	"database/sql"
)
import _ "github.com/go-sql-driver/mysql"

func NewMysqlConn() (db *sql.DB, err error) {
	db, err = sql.Open("mysql", "root:root123@tcp(127.0.0.1:3306)/imooc?charset=utf8")
	return
}

func GetResultRow(rows *sql.Rows) map[string]string {
	columns, _ := rows.Columns()
	scanArgs := make([]interface{}, len(columns))
	values := make([][]byte, len(columns))
	for j := range values {
		scanArgs[j] = &values[j]
	}

	record := make(map[string]string)
	for rows.Next() {
		rows.Scan(scanArgs...)

		for i, v := range values {
			if v != nil {
				record[columns[i]] = string(v)
			}
		}
	}

	return record
}


func GetResultRows(rows *sql.Rows) map[int]map[string]string {
	columns, _ := rows.Columns()
	vals := make([][]byte, len(columns))
	scans := make([]interface{}, len(columns))
	for k, _ := range vals {
		scans[k] = &vals[k]
	}

	i := 0
	result := make(map[int]map[string]string)
	for rows.Next() {
		rows.Scan(scans...)
		row := make(map[string]string)
		for k, v := range vals {
			key := columns[k]
			row[key] = string(v)
		}

		result[i] = row
		i++
	}
	return result
}

db数据库连接

package repositories

import (
	"database/sql"
	"imooc-product/common"
	"imooc-product/datamodels"
	"strconv"
)

//先开发接口
//实现定义的接口

type IProduct interface {
	//连接数据库
	Conn() error
	Insert(*datamodels.Product)(int64, error)
	Delete(int64) bool
	Update(*datamodels.Product) error
	SelectByKey(int64)(*datamodels.Product, error)
	SelectAll() ([]*datamodels.Product, error)
	SubProductNum (productId int64) error
}

type ProductManager struct {
	table string
	mysqlConn *sql.DB
}

func NewProductManager(table string, db *sql.DB) IProduct {
    return &ProductManager{table:table,mysqlConn:db}
}

func (p *ProductManager) Conn() (err error) {
	if p.mysqlConn == nil {
		mysql, err := common.NewMysqlConn()
		if err != nil {
			return err
		}
		p.mysqlConn = mysql
	}

	if p.table == "" {
		p.table = "product"
	}

	return
}

5-5 商品repository开发—插入


func (p *ProductManager) Insert(product *datamodels.Product) (productId int64,err error) {
	if err = p.Conn();err != nil {
		return
	}

	sql := "insert into product(productName, productNum, productImage, productUrl) values (?, ?, ?, ?)"
	stmt, err := p.mysqlConn.Prepare(sql)
	if err != nil {
		return
	}

	result, err := stmt.Exec(product.ProductName, product.ProductNum, product.ProductImage, product.ProductUrl)
	if err != nil {
		return
	}

	productId, err = result.LastInsertId()
	return
}

5-6 商品repository开发—删除&更新

func (p *ProductManager) Delete(productId int64) bool {
	if err := p.Conn();err != nil {
		return false
	}

	sql := "delete from product where id = ?"
	stmt, err := p.mysqlConn.Prepare(sql)
	if err != nil {
		return false
	}

	_, err = stmt.Exec(productId)
	if err != nil {
		return false
	}
	return true
}

func (p *ProductManager) Update(product *datamodels.Product) error {
	if err := p.Conn();err != nil {
		return err
	}

	sql := "update product set productName = ? , productNum = ? , productImage = ?, productUrl = ? where id=" + strconv.FormatInt(product.Id, 10)
	stmt, err := p.mysqlConn.Prepare(sql)
	if err != nil {
		return err
	}

	_, err = stmt.Exec(product.ProductName, product.ProductNum, product.ProductImage, product.ProductUrl)
	if err != nil {
		return err
	}

	return nil
}

5-7 商品repository开发—查询单条记录

func (p *ProductManager) SelectByKey(productId int64) (product *datamodels.Product, err error) {
	if err = p.Conn();err != nil {
		return &datamodels.Product{}, err
	}

	sql := "select * from " + p.table + " where id = " + strconv.FormatInt(productId, 10)
	row, err := p.mysqlConn.Query(sql)
	if err != nil {
		return &datamodels.Product{}, err
	}
	defer row.Close()

	result := common.GetResultRow(row)
	if len(result) == 0 {
		return &datamodels.Product{}, nil
	}

	product = &datamodels.Product{}
	common.DataToStructByTagSql(result, product)
	return product, nil
}

查找到条记录

func GetResultRow(rows *sql.Rows) map[string]string {
	columns, _ := rows.Columns()
	scanArgs := make([]interface{}, len(columns))
	values := make([][]byte, len(columns))
	for j := range values {
		scanArgs[j] = &values[j]
	}

	record := make(map[string]string)
	for rows.Next() {
		rows.Scan(scanArgs...)

		for i, v := range values {
			if v != nil {
				record[columns[i]] = string(v)
			}
		}
	}

	return record
}

根据结构体中sql标签映射数据到结构体中并且转换类型

package common

import (
	"errors"
	"reflect"
	"strconv"
	"time"
)

func DataToStructByTagSql(data map[string]string, obj interface{}) {
	objValue := reflect.ValueOf(obj).Elem()
	for i := 0; i < objValue.NumField(); i++ {
		//获取sql对应的值
		value := data[objValue.Type().Field(i).Tag.Get("sql")]
		//获取对应字段的名称
		name := objValue.Type().Field(i).Name
		//获取对应字段类型
		structFieldType := objValue.Field(i).Type()
		//获取变量类型,也可以直接写"string类型"
		val := reflect.ValueOf(value)
		var err error
		if structFieldType != val.Type() {
			//类型转换
			val, err = TypeConversion(value, structFieldType.Name()) //类型转换
			if err != nil {

			}
		}
		//设置类型值
		objValue.FieldByName(name).Set(val)
	}
}

查找全部


func (p *ProductManager) SelectAll() ([]*datamodels.Product,error) {
	if err := p.Conn();err != nil {
		return nil, err
	}

	sql := "select * from " + p.table
	rows,err := p.mysqlConn.Query(sql)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	result := common.GetResultRows(rows)
	if len(result) == 0 {
		return nil, nil
	}

	productArray := make([]*datamodels.Product, 0)
	for _, v := range result {
		product := &datamodels.Product{}
		common.DataToStructByTagSql(v, product)

		productArray = append(productArray, product)
	}

	return productArray, nil
}

func (p *ProductManager) SubProductNum(productId int64) error {
	if err := p.Conn();err != nil {
		return err
	}

	sql := "update " + p.table + " set productNum=productNum -1 where id=" + strconv.FormatInt(productId, 10)
	stmt, err := p.mysqlConn.Prepare(sql)
	if err != nil {
		return err
	}

	_, err = stmt.Exec()
	return err
}

5-9 主函数main.go功能讲解

package main

import (
	gcontext "context"
	"github.com/kataras/iris"
	"github.com/kataras/iris/context"
	"github.com/kataras/iris/mvc"
	"imooc-product/backend/web/controllers"
	"imooc-product/common"
	"imooc-product/repositories"
	"imooc-product/services"
	"log"
)

func main() {
	//创建iris实例
	app := iris.New()

	//设置错误模式,在mvc模式下提示错误
	app.Logger().SetLevel("debug")

	//注册模版
	template := iris.HTML("./backend/web/views", ".html").Layout("shared/layout.html").Reload(true)
	app.RegisterView(template)

	//设置模版目标
	app.StaticWeb("/assets", "./backend/web/assets")

	//出现异常跳转导指定页面
	app.OnAnyErrorCode(func(ctx context.Context) {
		ctx.ViewData("message", ctx.Values().GetStringDefault("message", "访问的页面出错!"))
		ctx.ViewLayout("")
		ctx.View("shared/error.html")
	})

	//注册控制器
	db, err := common.NewMysqlConn()
	if err != nil {
		log.Fatal(err)
	}


	ctx, cancel := gcontext.WithCancel(gcontext.Background())
	defer cancel()


	// NewProductManager中定义所有的db sql查询
	productRepository := repositories.NewProductManager("product", db)
	// services层用来配合controller层与db交互
	productService := services.NewProductService(productRepository)
	productParty := app.Party("/product")
	product := mvc.New(productParty)
	product.Register(ctx, productService)
	product.Handle(new(controllers.ProductController))

	orderRepository := repositories.NewOrderManagerRepository("`order`", db)
	orderService := services.NewOrderService(orderRepository)
	orderParty := app.Party("/order")
	order := mvc.New(orderParty)
	order.Register(ctx, orderService)
	order.Handle(new(controllers.OrderController))

	//启动服务
	app.Run(
		iris.Addr("localhost:8081"),
		iris.WithoutServerError(iris.ErrServerClosed),
		iris.WithOptimizations,
	)
}

5-10 商品管理功能Controller&View开发

services.IProductService 存放着对数据库的操作

获取所有的商品

package controllers

import (
	"github.com/kataras/iris"
	"github.com/kataras/iris/mvc"
	"imooc-product/common"
	"imooc-product/datamodels"
	"imooc-product/services"
	"strconv"
)

type ProductController struct {
	Ctx iris.Context
	ProductService services.IProductService
}

func (p *ProductController) GetAll() mvc.View {
	productArray , _ := p.ProductService.GetAllProduct()
	return mvc.View{
		Name:"product/view.html",
		Data:iris.Map{
			"productArray":productArray,
		},
	}
}

更新所有商品

func (p *ProductController) PostUpdate() {
	product := &datamodels.Product{}
	p.Ctx.Request().ParseForm()
	dec := common.NewDecoder(&common.DecoderOptions{TagName:"imooc"})
	if err := dec.Decode(p.Ctx.Request().Form, product);err != nil {
		p.Ctx.Application().Logger().Debug(err)
	}

	err := p.ProductService.UpdateProduct(product)
	if err != nil {
		p.Ctx.Application().Logger().Debug(err)
	}

	p.Ctx.Redirect("/product/all")
}
	// NewProductManager中定义所有的db sql查询
	productRepository := repositories.NewProductManager("product", db)
	// services层用来配合controller层与db交互
	productService := services.NewProductService(productRepository)
	productParty := app.Party("/product")
	product := mvc.New(productParty)
	product.Register(ctx, productService)
	product.Handle(new(controllers.ProductController))

商品、用户、订单

的结构 都类似,就不再描述了。。。

8-3 商品详情展示页面—访问权限设置(middleware)

一些总结

package encrypt

import (
	"crypto/aes"
	"crypto/cipher"
	"encoding/base64"
)

var PwdKey = []byte("fwerc3e2ex21e234x3565v4v")

// aes加密+crt
func aesEncrypt(plainText, key []byte) ([]byte, error) {
	// 1.创建一个底层使用aes的密码接口的对象
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}
	// 3.创建一个使用ctr加密接口对象
	iv := []byte("12345678abcdefgh") //初始化向量
	stream := cipher.NewCTR(block, iv)
	// 4.加密
	cipherText := make([]byte, len(plainText))
	stream.XORKeyStream(cipherText, plainText)
	return cipherText, nil
}

// aes解密
func aesDecrypt(cipherText, key []byte) []byte {
	block, err := aes.NewCipher(key)
	if err != nil {
		panic(err)
	}
	iv := []byte("12345678abcdefgh")
	stream := cipher.NewCTR(block, iv)
	plainText := make([]byte, len(cipherText))
	stream.XORKeyStream(plainText, cipherText)
	return plainText
}

// base64编码密文
func EnPwdCode(pwd []byte) (string, error) {
	result, err := aesEncrypt(pwd, PwdKey)
	if err != nil {
		return "", err
	}
	return base64.StdEncoding.EncodeToString(result), nil
}

// base64解码,并解密aes
func DePwdCode(pwd string) ([]byte, error) {
	pwdByte, err := base64.StdEncoding.DecodeString(pwd)
	if err != nil {
		return nil, err
	}
	return aesDecrypt(pwdByte, PwdKey), nil
}

package middleware

import (
	"imooc-product/encrypt"

	"github.com/kataras/golog"
	"github.com/kataras/iris"
)

// 校验cookie是否存在
func AuthConProduct(ctx iris.Context) {
	userID := ctx.GetCookie("uid")
	signKey := ctx.GetCookie("sign")
	if userID == "" || signKey == "" {
		golog.Debug("用户未登录")
		ctx.Redirect("/user/login")
		return
	}
	strByte, err := encrypt.DePwdCode(signKey)
	if err != nil {
		golog.Error("cookie解密失败", err)
		return
	}
	if userID != string(strByte) {
		golog.Debug("用户校验失败")
		return
	}
	golog.Debug("当前用户校验成功", userID)
	ctx.Next()
}

8-5 商品数据控制

【Go】Go-Iris框架实战之电商秒杀系统①后端开发_第8张图片

你可能感兴趣的:(#,go,framework)