zoukankan      html  css  js  c++  java
  • golang 重构博客统计服务

    欢迎关注楼主与他的小伙伴们的小站,每周分享一些技术文章,让我们在技术上一起成长------> 戳这里,欢迎光临小站 -_-

    作为一个后端开发,在docker,etcd,k8s等新技术不断涌现的今天,其背后的功臣golang在语言排行榜上持续走高,因此楼主也就开了这次使用golang自己开发的基础功能的二次装逼之旅。

    源于Spring Boot

    感兴趣的小伙伴可以看看楼主的上一篇,基于Spring Boot实现的功能,请移步使用Spring Boot实现博客统计服务

    实现redis存储逻辑

    选择redis而没选择数据库的原因是redis提供了丰富的数据结构与数据持久化策略,另外redis是基于内存的,相对于数据库来说,快了不止一个数量级。而统计阅读次数的场景对接口处理的速度还是有一定的要求的,因此楼主选择了redis作为阅读次数统计的db。
    下面就是redis操作的基础代码,比较简单楼主贴一下代码,不做进一步的阐述。

    • redigo依赖下载
    go get github.com/gomodule/redigo/redis
    
    • redis操作的工具类
    func initRedisPool() {
    	// 建立连接池
    	RedisClient = &redis.Pool{
    		// 从配置文件获取maxidle以及maxactive,取不到则用后面的默认值
    		MaxIdle:     1,
    		MaxActive:   10,
    		IdleTimeout: 180 * time.Second,
    		Dial: func() (redis.Conn, error) {
    			c, err := redis.Dial("tcp", RedisAddress)
    			if err != nil {
    				return nil, err
    			}
    			// 选择db
    			c.Do("SELECT", RedisDb)
    			return c, nil
    		},
    	}
    }
    
    /**
     * 设置redis的对应key的value
     */
    func redisSet(key string, value string) {
    	c, err := RedisClient.Dial()
    	if err != nil {
    		fmt.Println("Connect to redis error", err)
    		return
    	}
    	_, err = c.Do("SET", key, value)
    	if err != nil {
    		fmt.Println("redis set failed:", err)
    	}
    }
    
    /**
     * 获取redis的对应key的value
     */
    func redisGet(key string) (value string) {
    	c, err := RedisClient.Dial()
    	if err != nil {
    		fmt.Println("Connect to redis error", err)
    		return
    	}
    	val, err := redis.String(c.Do("GET", key))
    	if err != nil {
    		fmt.Println("redis get failed:", err)
    		return ""
    	} else {
    		fmt.Printf("Got value is %v 
    ", val)
    		return val
    	}
    }
    
    /**
     * redis使得对应的key的值自增
     */
    func redisIncr(key string) (value string) {
    	c, err := RedisClient.Dial()
    	_, err = c.Do("INCR", key)
    	if err != nil {
    		fmt.Println("incr error", err.Error())
    	}
    
    	incr, err := redis.String(c.Do("GET", key))
    	if err == nil {
    		fmt.Println("redis key after incr is : ", incr)
    	}
    	return incr
    }
    
    

    博客阅读次数统计接口实现

    博客阅读次数统计的基本业务逻辑就是,对应每篇博客的blogId作为redis的key,而访问次数就是这个key所对应的value,每访问一次该接口就要将对应的blogId自增一次,并返回对应的value。这里楼主选择的redis的数据结构是redis的Stirng,下面是楼主实现该逻辑的主要代码:

    
    package main
    
    import (
    	"encoding/json"
    	"fmt"
    	"github.com/garyburd/redigo/redis"
    	"log"
    	"net/http"
    	"time"
    	"strings"
    )
    
    const RedisAddress = "127.0.0.1:6379"
    const RedisDb = 0
    
    const AllowRequestUrlH = "*"
    const  AllowRequestUrlW = "*"
    const  IllegalCharacters = "?"
    const  DefaultReadCount = "1"
    
    var (
    	// 定义常量
    	RedisClient *redis.Pool
    )
    
    func main() {
    	// 初始化redis连接池
    	initRedisPool()
    
    	// 启动web服务监听
    	http.HandleFunc("/*-*/*/", blogReadCountIncr)       //设置访问的路由
    	err := http.ListenAndServe(":9401", nil) //设置监听的端口
    	if err != nil {
    		log.Fatal("ListenAndServe: ", err)
    	}
    }
    
    func blogReadCountIncr(responseWriter http.ResponseWriter, request *http.Request) {
    
    	// 解析参数,默认不解析
    	request.ParseForm()
    
    	blogId := request.Form.Get("blogId")
    
    	log.Println(">>>>>> method blogReadCountIncr exec , request params is : ",blogId)
    
    	// 判断请求参数是否为空
    	if "" == blogId {
    		result := ResultCode{
    			Code: 200,
    			Msg:  "success",
    		}
    
    		ret, _ := json.Marshal(result)
    		fmt.Fprintf(responseWriter, string(ret)) //这个写入到w的是输出到客户端的
    	}
    	
    	readCount := redisGet(blogId)
    	if "" == readCount {
    		// 不符合规则,直接返回
    		flag := strings.Index(blogId, AllowRequestUrlH) != 0 ||strings.Index(blogId, AllowRequestUrlW) != 0||strings.Contains(blogId, IllegalCharacters)
    		if  !flag {
    			result := ResultCode{
    				Code: 200,
    				Msg:  "success",
    			}
    
    			ret, _ := json.Marshal(result)
    			fmt.Fprintf(responseWriter, string(ret)) //这个写入到w的是输出到客户端的
    		}
    
    		redisSet(blogId, DefaultReadCount)
    		readCount = DefaultReadCount
    	} else {
    		readCount = redisIncr(blogId)
    	}
    	log.Println(">>>>>> readCount is : ",readCount)
    	result := ResultCode{
    		Code: 200,
    		Msg:  "success",
    		Data: readCount,
    	}
    	ret, _ := json.Marshal(result)
    	fmt.Fprintf(responseWriter, string(ret)) //这个写入到w的是输出到客户端的
    }
    // 结构体定义返回值
    type ResultCode struct {
    	Msg  string `json:"msg"`
    	Code int    `json:"code"`
    	Data string `json:"data"`
    }
    
    

    实现过程中遇到的坑

    出现的问题

    使用golang原生的json工具序列化时,出现序列化失败的问题,如下所示的结构体定义,乍一看是没啥问题的,然而使用

    ret, _ := json.Marshal(result)
    

    序列化时,出现无法序列化成json串的问题,另外还不报错,这让楼主很是头疼。

    type ResultCode struct {
    	msg  string `json:"msg"`
    	code int    `json:"code"`
    	data string `json:"data"`
    }
    

    问题解决

    最终楼主通过各种姿势的排查,发现是结构体定义有问题,当定义结构体时首字母必须大写才能序列化成功,这个特点在golang里面很是明显,在函数调用时首字母小写的函数在其他文件里面是调不到的。下面给出正确的结构体定义

    type ResultCode struct {
    	Msg  string `json:"msg"`
    	Code int    `json:"code"`
    	Data string `json:"data"`
    }
    

    小结

    目前很多大佬都写过关于golang web的教程,如有雷同,请略过不看,本文通过自己的亲身实战以及楼主自己踩到的坑完成的,另外本文是基于go内置的net/http库实现的web服务。

    号外

    楼主造了一个轮子,LIGHTCONF 是一个基于Netty实现的一个配置管理平台,其核心设计目标是“为业务提供统一的配置管理服务”,可以做到开箱即用。感兴趣的给个star支持一下。

  • 相关阅读:
    Java8新特性
    中文乱码常见解决方案
    Btrace的使用方法
    jquery获取的html元素和document获取的元素的区别
    Easy UI分页控件修改刷新方法后触发两次请求
    js获取字符串的实际长度并截断实际长度
    js生成唯一的uuid
    easy UI动态赋值
    springmvc+shiro认证框架配置
    shiro配置unauthorizedUrl,无权限抛出无权限异常,但是不跳转
  • 原文地址:https://www.cnblogs.com/haifeiWu/p/9351976.html
Copyright © 2011-2022 走看看