主要任务
DAY1:
- 在main.go中写了一个简单键值对增删改查
/*写在前头
1.这是用go实现简单的K-V存储,包括了简单的增删改查
*/
package main // 声明 main 包,表明当前是一个可执行程序
import (
"bufio"
"fmt"
"os"
"strings"
) // 导入内置 fmt 包
type myElement struct { //新建 我的元素 类 里面包括名字 surname 跟唯一的id值
Name string
SurName string
Id string
}
//用原生的go map来实现K-V春初,map变量被申明为全局变量
var DATA = make(map[string]myElement) //键值对 string - myElement
/***********增删该查函数******************/
func ADD(k string, n myElement) bool { //返回是否成功
if k == "" {
return false
}
if LOOKUP(k) == nil { //如果已存在 就是白
DATA[k] = n
return true
}
return false
}
func DELETE(k string) bool {
if LOOKUP(k) != nil { //不存在就失败
delete(DATA, k)
return true
}
return false
}
func LOOKUP(k string) *myElement {
_, ok := DATA[k]//根据所给键查找 对应map,如果有就返回地址对应的值,否则返回nil
if ok {
n := DATA[k]//获得对应的键值对地址,返回
return &n
} else {
return nil
}
}
func CHANGE(k string, n myElement) bool {
DATA[k] = n
return true
}
func PRINT() {
for k, v := range DATA {//打印整个data
fmt.Printf("key: %s value: %v", k, v)
}
}
func main() {//io流
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
text := scanner.Text()//获取当前输入的字符串
text = strings.TrimSpace(text)//前后清除空格
tokens := strings.Fields(text)//返回切片
switch len(tokens) {
case 0://如果长度为0,代表没东西
continue
case 1://无论输入的是多少个,最终拼接为5个长
tokens = append(tokens, "")
tokens = append(tokens, "")
tokens = append(tokens, "")
tokens = append(tokens, "")
case 2:
tokens = append(tokens, "")
tokens = append(tokens, "")
tokens = append(tokens, "")
case 3:
tokens = append(tokens, "")
tokens = append(tokens, "")
case 4:
tokens = append(tokens, "")
}
switch tokens[0] {//判断是要干嘛
case "PRINT"://打印全部
PRINT()
case "STOP"://停止 推出
return
case "DELETE":
if !DELETE(tokens[1]) {
fmt.Println("Delete operations failed")
}
case "ADD":
n := myElement{tokens[2], tokens[3], tokens[4]}
if !ADD(tokens[1], n) {
fmt.Println("Add operation failed")
}
case "LOOKUP":
n := LOOKUP(tokens[1])
if n != nil {
fmt.Printf("%v\n", n)
}
case "CHANGE":
n := myElement{tokens[2], tokens[3], tokens[4]}
if !CHANGE(tokens[1], n) {
fmt.Println("Update operation failed")
}
default:
fmt.Println("Unknown command - please try again!")
}
}
}
- 安装gin并且 www.cnblogs.com/tudaogaoyan… 原型作为框架
- 下载redis 与mogodb
- 安装mogodb 并且让go链接他
package main
//go链接数据库
import (
"fmt"
"log"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
type Person struct {
Id int
First_name string
}
func main() {
session, err := mgo.Dial("localhost:27017")
if err != nil {
panic(err)
}
defer session.Close()
// Optional. Switch the session to a monotonic behavior.
session.SetMode(mgo.Monotonic, true)
c := session.DB("test").C("people")
err = c.Insert(&Person{11, "13478808311"},
&Person{13, "15040268074"})
if err != nil {
log.Fatal(err)
}
var persons []Person
err = c.Find(bson.M{}).All(&persons) //把数据丢入persons里
fmt.Println(persons) //看记录长啥样子
// fmt.Println("Name:", result.Id)
// fmt.Println("Phone:", result.First_name)
}
- www.cnblogs.com/spnt/p/4686… 切换成连接池
- 学习gin www.cnblogs.com/-beyond/p/9… 入门知识
- 学习mogodb基本语句 www.runoob.com/mongodb/mon…
Day2 1.
- restful风格的增删改查,链接数据库直接进行操作
什么是restful风格 跟以往的区别 www.jianshu.com/p/7893169a7… www.jianshu.com/p/a2f067cd4… 如何做
1. 控制器
创建 controllers 文件夹和对应的文件 movies.go
movies.go
2.路由
创建一个 routes文件夹,并创建对应的文件 routes.go
routes.go
3. Models
创建 models 文件夹和对应的文件 db.go(数据层),封装对MongoDB的封装
4.业务逻辑层 models/movies.go
前端与restful两种风格
链接时出现的问题 www.cnblogs.com/fengluzhewe… blog.csdn.net/qq_38402659… 必看!!! blog.csdn.net/qq_40143332… 操作
- 试着整合redis
redis作为二级缓存 只读不写,在代码中试着改写该功能 blog.csdn.net/weixin_4269… 参考 redis 与mogondb的整合test
package main
import (
"fmt"
//此处注意“_”表示引用mysql函数中init的方法而无需使用函数
"github.com/garyburd/redigo/redis"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
type Person struct {
Id string `json:"id" form:"id"`
First_name string `json:"first_name" form:"first_name"`
// LastName string `json:"last_name" form:"last_name"`
}
func main() {
var cmd string
for {
fmt.Println("请输入命令:")
fmt.Scan(&cmd)
//fmt.Println("你输入的是:",cmd)
switch cmd { //如果命令是getall
case "getall":
GetAll()
default:
fmt.Println("不能识别的命令")
}
fmt.Println()
}
}
func GetAll() {
//先看看redis里有没有数据
conn, _ := redis.Dial("tcp", "localhost:6379") //链接redis库
defer conn.Close()
reply, err := conn.Do("lrange", "mlist", 0, -1) //获取所有值,
pkeys, _ := redis.Strings(reply, err) //把值仍入pkeys中
fmt.Println(pkeys)
if len(pkeys) > 0 {
//如果有
fmt.Println("从redis获得数据")
// 从redis里直接读取
for _, key := range pkeys {
retStrs, _ := redis.Strings(conn.Do("hgetall", key))
//fmt.Println(retStrs)
fmt.Printf("{%s}\n", retStrs[1])
}
} else {
//如果没有
fmt.Println("从mongodb获得数据")
session, err := mgo.Dial("localhost:27017") //链接mongodb
if err != nil {
panic(err)
}
defer session.Close()
// Optional. Switch the session to a monotonic behavior.
session.SetMode(mgo.Monotonic, true)
c := session.DB("test").C("people")
var persons []Person
err = c.Find(bson.M{}).All(&persons) //把数据丢入persons里
fmt.Println(persons) //看记录长啥样子
//写入redis并且设置过期时间
for _, p := range persons {
//将p以hash形式写入redis
_, e1 := conn.Do("hmset", p.Id, "name", p.First_name)
//将这个hash的key加入mlist
_, e2 := conn.Do("rpush", "mlist", p.Id)
//设置过期时间
_, e3 := conn.Do("expire", p.Id, 60) //60s就过期
_, e4 := conn.Do("expire", "mlist", 60)
if e1 != nil || e2 != nil || e3 != nil || e4 != nil {
fmt.Println(p.First_name, "写入失败", e1, e2, e3, e4) //上面哪一步随意一部错就这样
} else {
fmt.Println(p.First_name, "写入成功")
}
}
}
}
- 配置文件yaml学习
blog.csdn.net/yuyinghua03… yaml读取 www.cnblogs.com/zhaof/p/895… 具体使用
配置文件需要应对的几种场景
- IDE运行/调试时期读取配置文件
- 运行单测或者benchmark Test的时候读取配置文件
- 可执行文件(部署文件)读取配置文件
package database
//go链接数据库,数据层
import (
"database/sql"
"fmt"
"io/ioutil"
"gopkg.in/mgo.v2"
"gopkg.in/yaml.v2"
)
type Mongodbyaml struct {
Host string `yaml:"host"`
User string `yaml:"user"`
Pwd string `yaml:"pwd"`
Dbname string `yaml:"dbname"`
Cname string `yaml:"cname"`
}
type Config struct {
MongodbyamlConfig Mongodbyaml `yaml:"Mongodbyaml"`
}
func (c *Config) getConf() *Config {
yamlFile, err := ioutil.ReadFile("conf.yaml") // // yaml解析的时候c.data如果没有被初始化,会自动为你做初始化
if err != nil {
fmt.Println(err.Error())
}
err = yaml.Unmarshal(yamlFile, c) //把值装进c中
if err != nil {
fmt.Println(err.Error())
}
return c
}
var SqlDB *sql.DB
func init() {
var c Config //c是conf类 与配置文件的相同,并且在同一目录下
conf := c.getConf() //方法 获取配置文件
SqlDB, err := mgo.Dial(conf.MongodbyamlConfig.Host) //连接 正确返回session否则返回err
if err != nil {
panic(err) //如果确实有错 则报出来
}
defer SqlDB.Close() //关闭
// Optional. Switch the session to a monotonic behavior.
SqlDB.SetMode(mgo.Monotonic, true) //设置读写模式
}
func ConnecToDB() *mgo.Session { //连接数据库返回给models
var c Config //c是conf类 与配置文件的相同,并且在同一目录下
conf := c.getConf() //方法 获取配置文件
session, err := mgo.Dial(conf.MongodbyamlConfig.Host)
if err != nil {
panic(err)
}
//defer session.Close()
session.SetMode(mgo.Monotonic, true)
return session
}
4.openresty反向代理学习
www.jianshu.com/p/33d4a3fdc… nginx在go中 blog.csdn.net/weixin_3811… 反向代理的作用 www.jianshu.com/p/0b6f80949…
server {
listen 8081;
server_name localhost;
location / {
proxy_pass http://localhost:8080/;
}
}
5.压力测试ab
www.cnblogs.com/crazycoderl… windows 下载安装 blog.csdn.net/qq_26525215… 具体意思 www.jianshu.com/p/a22174de2… 必看
先让项目跑起来,cmd 进入bin 目录下 开启服务 调用命令
6.利用prrof和火焰图分析瓶颈
在router.go中加入这一行
err := http.ListenAndServe(":9909", nil)//pprof 火焰图分析瓶颈
if err != nil {
panic(err)
}
8.redis 实现
redis 实现分页
www.cnblogs.com/ricklz/p/95… redis 实现语法
godis 基本格式
9.map 与rabbiemq
blog.51cto.com/13447608/24… 消息队列与go blog.csdn.net/weixin_3973… 安装 blog.51cto.com/13447608/24… 试例 www.cnblogs.com/chaselogs/p… 实例2 开两个窗口发消息(提供方与消费方) blog.csdn.net/zhghost/art… 必看!!!!! www.jianshu.com/p/ab64681be… 为什么要主从 blog.csdn.net/zhghost/art… 不同路由!!!
package models
import (
"fmt"
"log"
"strings"
"github.com/streadway/amqp"
)
const MQURL = "amqp://guest:guest@127.0.0.1:5672/"
//创建rabbitmq结构体实例
type RabbitMQ struct {
conn *amqp.Connection
channel *amqp.Channel
QueueName string
Exchange string
Key string
Mqurl string
}
func NewRabbitMQ(queueName string, Exchange string, key string) *RabbitMQ {
rabbitmq := &RabbitMQ{QueueName: queueName, Exchange: Exchange, Key: key, Mqurl: MQURL}
var err error
rabbitmq.conn, err = amqp.Dial(rabbitmq.Mqurl)
rabbitmq.failOnErr(err, "创建连接错误")
rabbitmq.channel, err = rabbitmq.conn.Channel()
rabbitmq.failOnErr(err, "获取channel失败")
return rabbitmq
}
//断开channel和connection
func (r *RabbitMQ) Destroy() {
r.channel.Close()
r.conn.Close()
}
//错误处理函数
func (r *RabbitMQ) failOnErr(err error, message string) {
if err != nil {
log.Fatalf("%s:%s", message, err)
panic(fmt.Sprintf("%s%s", message, err))
}
}
//simple模式step1: rabbitmq的实例
func NewRabbitMQSimple(queueName string) *RabbitMQ {
return NewRabbitMQ(queueName, "", "")
}
//简单模式step:2.简单模式下生产代码
func (r *RabbitMQ) PublishSimple(message string) {
//申请队列,如果队列不存在会自动创建,如果存在则跳过创建
//保证队列存在,消息能发送到队列中
_, err := r.channel.QueueDeclare(
r.QueueName,
//是否持久化
false,
//是否为自动删除
false,
//是否具有排他性
false,
//是否阻塞
false,
//额外属性
nil,
)
if err != nil {
fmt.Println(err)
}
//发送消息到队列中
err = r.channel.Publish(
r.Exchange,
r.QueueName,
//如果为true,根据exchange类型和routkey规则,如果无法找到符合条件的队列那么会把发送的消息返回给发送者
false,
false,
amqp.Publishing{
ContentType: "text/plain",
Body: []byte(message),
})
if err != nil {
fmt.Println(err)
}
}
func (r *RabbitMQ) ConsumeSimple() {
_, err := r.channel.QueueDeclare(
r.QueueName,
//是否持久化
false,
//是否为自动删除
false,
//是否具有排他性
false,
//是否阻塞
false,
//额外属性
nil,
)
if err != nil {
fmt.Println(err)
}
msgs, err := r.channel.Consume(
r.QueueName,
//用来区分多个消费者
"",
//是否自动应答
true,
//是否具有排他性
false,
//如果设置为true,表示不能将同一个connection中发送的消息传递给这个connection中的消费者
false,
//队列消费是否阻塞
false,
//其他属性
nil,
)
if err != nil {
fmt.Println(err)
}
forever := make(chan bool)
//启用协程处理消息
go func() {
for d := range msgs {
//实现我们要处理的逻辑函数
log.Printf("Received a message:%s")
fmt.Println(string(d.Body))
stringbody := string(d.Body)
b := strings.Split(stringbody, "///")
if b[0] == "add" { //如果表示新增,就往从库中加
DATA2[b[1]] = b[2]
} else if b[0] == "edit" {
DATA2[b[1]] = b[2]
} else if b[0] == "delete" {
delete(DATA2, b[1]) // 删除不存在的key,原m不影响
}
fmt.Println(DATA2)
}
}()
<-forever
}
- 画设计类图
- 单元测试
- github.com/guyan0319/g… 测试单元测试
- blog.csdn.net/weiaiyishen…
- blog.csdn.net/u011957758/…
- blog.csdn.net/zyou1986/ar… 代码覆盖率
- learnku.com/go/t/38710 必看!!
看覆盖率资料!
红色为未覆盖 绿色的则是已覆盖