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 \n", 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支持一下。

作 者:haifeiWu 原文链接:http://www.hchstudio.cn/article/2018/2622/ 版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java开发者杂谈

Netty(1):第一个netty程序

为什么选择Netty   netty是业界最流行的NIO框架之一,它的健壮型,功能,性能,可定制性和可扩展性都是首屈一指的,Hadoop的RPC框架Avro就使...

4067
来自专栏博客园

讲一下Asp.net core MVC2.1 里面的 ApiControllerAttribute

转自:https://www.cnblogs.com/sheldon-lou/p/9495377.html

1132
来自专栏dotnet & java

讲一下Asp.net core MVC2.1 里面的 ApiControllerAttribute

ASP.NET Core MVC 2.1 特意为构建 HTTP API 提供了一些小特性,今天主角就是 ApiControllerAttribute. (注:文...

1302
来自专栏JadePeng的技术博客

Docker+Jenkins持续集成环境(5): android构建与apk发布

项目组除了常规的java项目,还有不少android项目,如何使用jenkins来实现自动构建呢?本文会介绍安卓项目通过jenkins构建的方法,并设计开发一个...

5048
来自专栏DOTNET

ASP.NET MVC编程——单元测试

1自动化测试基本概念 自动化测试分为:单元测试,集成测试,验收测试。 单元测试 检验被测单元的功能,被测单元一般为低级别的组件,如一个类或类方法。 单元测试要满...

5255
来自专栏肖蕾的博客

第一章:Hello Libgdx

https://gitee.com/xcode_xiao/LibGdxDemos2/tree/master/HelloGDX

961
来自专栏Pythonista

牛掰的python与unix

  加载subprocess模块仅仅是将可以使用的代码文件加载进来。也可以创建自己的模块或文件,拱以后重复使用,这与加载subprocess模块的方法相同。IP...

1122
来自专栏JackieZheng

探秘Tomcat——启动篇

tomcat作为一款web服务器本身很复杂,代码量也很大,但是模块化很强,最核心的模块还是连接器Connector和容器Container。具体请看下图: ? ...

4857
来自专栏chenssy

【死磕 Spring】----- IOC 之 IOC 初始化总结

前面 13 篇博文从源码层次分析了 IOC 整个初始化过程,这篇就这些内容做一个总结将其连贯起来。

1014
来自专栏chenssy

【死磕 Spring】----- IOC 之 IOC 初始化总结

前面 13 篇博文从源码层次分析了 IOC 整个初始化过程,这篇就这些内容做一个总结将其连贯起来。

861

扫码关注云+社区

领取腾讯云代金券