文章目录
开发工具:goland+postman
课程地址:https://www.bilibili.com/video/BV1Mi4y147fF?p=1
第一篇 系统设计
路由分组
v1 := router.Group("v1/topics")
v1.GET("/", topics)
v1.GET("/:topic_id",src.GetTopicDetail)
简单Dao层代码封装、使用中间件模拟 鉴权
类似于python的装饰器
func MustLoginin() gin.HandlerFunc {
return func(c *gin.Context){
if _,status := c.GetQuery("token");!status{
c.String(http.StatusUnauthorized, "缺少token参数")
// 不会往下走
c.Abort()
}else {
c.Next()
}
}
}
// 简单的dao层封装
func GetTopicDetail(c *gin.Context) {
c.String(200,"获取yopic为 %s 的帖子列表", c.Query("topic_id"))
}
func GetTopicList(c *gin.Context) {
c.String(200,"获取用户名 %s 的帖子列表", c.Query("user_name"))
}
func NewTopicList(c *gin.Context) {
c.String(200,"新增帖子")
}
func DelTopicList(c *gin.Context) {
c.String(200,"删除帖子")
}
package main
import (
"github.com/gin-gonic/gin"
"net/http"
"tocpic/src"
)
func topic_id(c *gin.Context) {
c.String(http.StatusOK, "获取帖子id = %s", c.Param("topic_id"))
}
func topics(c *gin.Context) {
if c.Query("username") == ""{
c.String(200,"获取帖子列表")
}else{
c.String(200,"获取用户名 %s 的帖子列表", c.Query("username"))
}
}
func main() {
router := gin.Default()
// 路由分组
v1 := router.Group("v1/topics")
v1.GET("/", topics)
v1.GET("/:topic_id",src.GetTopicDetail)
// 中间件限制登录
v1.Use(src.MustLoginin())
{
v1.POST("/",src.NewTopicList)
v1.DELETE("/:topic_id",src.DelTopicList)
}
router.Run(":5000")
}
05 6内置验证器的初步使用、POST参数绑定
postman测试
结构字段验证--validator.v9
结构体
type Topic struct {
TopicID int `json:"id" form:"id"`
TopicTitle string `json:"title" form:"title" binding:"min=4,max=20"`
TopicShortTitle string `json:"stitle" form:"stitle" binding:"required,nefield=TopicTitle"`
UserIP string `json:"ip" form:"ip" binding:"ipv4"`
TopicScore int `json:"score" form:"score" binding:"omitempty,gt=5"` // //omitempty要么不传,传的话就要大于5
TopicUrl string `json:"url" form:"url" binding:"omitempty" validate:"topicurl"`
}
自定义验证函数
package src
import (
"fmt"
"gopkg.in/go-playground/validator.v9"
"regexp"
)
func TopicUrl(v validator.FieldLevel) bool {
if _, ok := v.Top().Interface().(*Topic); ok {
// 正则匹配
if matched,_ := regexp.MatchString(`\w[4,10]$`,v.Field().String());matched {
return true
}
fmt.Println(v.Field().String())
//return str != "invalid"
}
return false
}
package main
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"gopkg.in/go-playground/validator.v9"
"net/http"
"tocpic/src"
)
func topic_id(c *gin.Context) {
c.String(http.StatusOK, "获取帖子id = %s", c.Param("topic_id"))
}
func topics(c *gin.Context) {
if c.Query("username") == ""{
c.String(200,"获取帖子列表")
}else{
c.String(200,"获取用户名 %s 的帖子列表", c.Query("username"))
}
}
func main() {
router := gin.Default()
if v, ok := binding.Validator.Engine().(*validator.Validate); ok { //类型断言
v.RegisterValidation("topicurl", src.TopicUrl) //注册调用tag和自定义验证器
}
v1 := router.Group("v1/topics")
v1.GET("/", src.GetTopicList)
v1.GET("/:topic_id",src.GetTopicDetail)
// 中间件限制登录
v1.Use(src.MustLoginin())
{
v1.POST("/",src.NewTopic)
v1.DELETE("/:topic_id",src.DelTopicList)
}
router.Run(":5000")
}func main() {
router := gin.Default()
validate := validator.New()
//自己定义topicurl标签以及与之对应的处理逻辑
validate.RegisterValidation("topicurl", src.TopicUrl)
//查看是否符合验证
err := validate.Struct(src.Topic{})
if err != nil {
fmt.Println(err)
}
v1 := router.Group("v1/topics")
v1.GET("/", src.GetTopicList)
v1.GET("/:topic_id",src.GetTopicDetail)
// 中间件限制登录
v1.Use(src.MustLoginin())
{
v1.POST("/",src.NewTopic)
v1.DELETE("/:topic_id",src.DelTopicList)
}
router.Run(":5000")
}
第二篇 ORM
08 9到底要不要用ORM、Gorm入手、执行原始SQL”
db, err := gorm.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/gin")
if err != nil {
fmt.Println(err)
}
rows, _ := db.Raw("select topic_id, topic_title from topic").Rows()
for rows.Next() {
var t_id int
var t_title string
rows.Scan(&t_id, &t_title)
fmt.Println(t_id, t_title)
}
defer db.Close()
09 10 结合Model进行数据映射,查询基本要点
db, err := gorm.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/gin")
if err != nil {
fmt.Println(err)
}
// 打印sql执行过程
db.LogMode(true)
// 结构体
var tcs []src.TopicClass
//tc := &src.TopicClass{}
// 查询出全部数据
db.Find(&tcs)
fmt.Println(tcs)
// 条件筛选
db.Where("class_name=?","2").Find(&tcs)
fmt.Println(tcs)
// 指定表名
//db.Table("topic_class").First(&tc)
//fmt.Println(tc)
rows, _ := db.Raw("select topic_id, topic_title from topic").Rows()
for rows.Next() {
var t_id int
var t_title string
rows.Scan(&t_id, &t_title)
fmt.Println(t_id, t_title)
}
defer db.Close()
10.结合Gin实现查询api
func GetTopicDetail(c *gin.Context) {
db, err := gorm.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/gin")
if err != nil {
fmt.Println(err)
}
defer db.Close()
// 打印sql执行过程
db.LogMode(true)
tid := c.Param("topic_id")
topics := Topics{}
db.Find(&topics, tid)
c.JSON(200, topics)
}
查询使用初始化db
src/TopicDao.go
package src
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
)
var (
DBHelper *gorm.DB
err error
)
// 初始化
func init() {
var err error
DBHelper, err := gorm.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/gin")
if err != nil {
fmt.Println(err)
}
// 打印sql执行过程
DBHelper.LogMode(true)
}
src/TopicDao.go
func GetTopicDetail(c *gin.Context) {
tid := c.Param("topic_id")
topics := Topics{}
DBHelper.Find(&topics, tid)
c.JSON(200, topics)
defer DBHelper.Close()
}
第三篇
Redis缓存、连接池
- redis配置 src/MyRedis.go
package src
import (
"fmt"
"github.com/garyburd/redigo/redis"
"time"
)
var RedisDefaultPool *redis.Pool
func newPool(addr string) *redis.Pool {
return &redis.Pool{
Dial: func() (redis.Conn, error) {
return redis.Dial("tcp", addr)
},
TestOnBorrow: nil,
MaxIdle: 3,
MaxActive: 0,
IdleTimeout: 210* time.Second,
Wait: false,
MaxConnLifetime: 0,
}
}
func init(){
RedisDefaultPool = newPool("127.0.0.1:6379")
}
func main() {
c, err := redis.Dial("tcp", "127.0.0.1:6379")
if err != nil {
fmt.Println("Connect to redis error", err)
return
}
defer c.Close()
}
获取redis的值
func main(){ coon := src.RedisDefaultPool.Get() ret, err := redis.String(coon.Do("get","name")) if err != nil { log.Println(err) return } log.Println(ret) }
结合gin实现基本的redis缓存、缓存穿透简单处理”
缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。
这里需要注意和缓存击穿的区别,缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
func GetTopicDetail(c *gin.Context) {
tid := c.Param("topic_id")
topics := Topics{}
coon := RedisDefaultPool.Get()
redisKey := "topic_" + tid
defer coon.Close()
ret, err := redis.Bytes(coon.Do("get", redisKey))
if err != nil { // 缓存没有
DBHelper.Find(&topics, tid)
retData, _ := ffjson.Marshal(topics)
// redis简单的缓存穿透
if topics.TopicID ==0 {
coon.Do("setex", redisKey, 20, retData)
}else {
coon.Do("setex", redisKey, 50, retData)
}
c.JSON(200, topics)
log.Println("从数据库读取")
} else { //有缓存
ffjson.Unmarshal(ret, &topics)
c.JSON(200, topics)
log.Println("从Redis读取")
}
defer DBHelper.Close()
}
实现Redis缓存的装饰器模式
将上面的dao层改为```
func GetTopicDetail(c *gin.Context) {tid := c.Param("topic_id") topics := Topics{} DBHelper.Find(&topics, tid) // 获取的数据都为dbResult c.Set("dbResult", topics) }
装饰器模式 实现Redis缓存```
package srcimport (
"fmt" "github.com/garyburd/redigo/redis" "github.com/gin-gonic/gin" "github.com/pquerna/ffjson/ffjson" "log"
)
// 缓存装饰器
func CacheDecorator(h gin.HandlerFunc, param string, redKeyPattern string, empty interface{}) gin.HandlerFunc {return func(context *gin.Context) { // redis判断 getID := context.Param(param) // 得到id redisKey := fmt.Sprintf(redKeyPattern, getID) //拼接redisKey coon := RedisDefaultPool.Get() //连接redis defer coon.Close() ret, err := redis.Bytes(coon.Do("get", redisKey)) if err != nil { // 缓存没有 h(context) //执行目标方法,从数据库得到dbResult dbResult, exists := context.Get("dbResult") if !exists{ dbResult = empty } retData, _ := ffjson.Marshal(dbResult) // 设置redis缓存 coon.Do("setex", redisKey, 20, retData) context.JSON(200,dbResult) } else { ffjson.Unmarshal(ret, &empty) context.JSON(200, dbResult) log.Println("从Redis读取") } //defer coon.Close() }
}
```
本文转自 https://blog.csdn.net/weixin_43746433/article/details/106153293,如有侵权,请联系删除。
部分资料来源于网络,版权属其原著者所有,只供学习交流之用。如有侵犯您的权益,请联系【公众号:码农印象】删除,可在下方评论,亦可邮件至ysluckly.520@qq.com。互动交流时请遵守宽容、换位思考的原则。