项目需求
主要技术栈
错误处理工具
package utils
import (
"fmt"
"os"
)
/*处理错误:有错误时暴力退出*/
func HandlerError(err error, when string) {
if err != nil {
fmt.Println(when, err)
os.Exit(1)
}
}
数学工具
package utils
import (
"time"
"math/rand"
"sync"
)
var(
//随机数互斥锁(确保GetRandomInt不能被并发访问)
randomMutex sync.Mutex
)
/*获取[start,end]范围内的随机数*/
func GetRandomInt(start, end int) int {
randomMutex.Lock()
<-time.After(1 * time.Nanosecond)
r := rand.New(rand.NewSource(time.Now().UnixNano()))
n := start + r.Intn(end-start+1)
randomMutex.Unlock()
return n
}
起名系统
package utils
var (
//姓氏
familyNames = []string{"赵", "钱", "孙", "李", "周", "吴", "郑", "王", "冯", "陈", "楚", "卫", "蒋", "沈", "韩", "杨", "张", "欧阳", "东门", "西门", "上官", "诸葛", "司徒", "司空", "夏侯"}
//辈分(宗的永其光...)
middleNamesMap = map[string][]string{}
//名字
lastNames = []string{"春", "夏", "秋", "冬", "风", "霜", "雨", "雪", "木", "禾", "米", "竹", "山", "石", "田", "土", "福", "禄", "寿", "喜", "文", "武", "才", "华"}
)
/*初始化姓氏和对应的辈分*/
func init() {
for _, x := range familyNames {
if x != "欧阳" {
middleNamesMap[x] = []string{"德", "惟", "守", "世", "令", "子", "伯", "师", "希", "与", "孟", "由", "宜", "顺", "元", "允", "宗", "仲", "士", "不", "善", "汝", "崇", "必", "良", "友", "季", "同"}
} else {
middleNamesMap[x] = []string{"宗", "的", "永", "其", "光"}
}
}
}
/*获得随机姓名*/
func GetRandomName() (name string) {
familyName := familyNames[GetRandomInt(0, len(familyNames)-1)]
middleName := middleNamesMap[familyName][GetRandomInt(0, len(middleNamesMap[familyName])-1)]
lastName := lastNames[GetRandomInt(0, len(lastNames)-1)]
return familyName + middleName + lastName
}
数据模型
package utils
/*考试成绩*/
type ExamScore struct {
Id int `db:"id"`
Name string `db:"name"`
Score int `db:"score"`
}
数据库工具
这份代码实现了二级缓存的核心功能,包括:
package utils
import (
"fmt"
/*MySQL*/
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
"github.com/garyburd/redigo/redis"
"errors"
"sync"
)
var(
//数据库读写锁
dbMutext sync.RWMutex
)
/*
通用Mysql查询工具
tableName 要查询的表名
argsMap 查询条件集合
dest 查询结果存储地址
*/
func QueryFromMysql(tableName string,argsMap map[string]interface{},dest interface{}) (err error) {
fmt.Println("QueryScoreFromMysql...")
//写入期间不能进行数据库读访问
dbMutext.RLock()
db, err := sqlx.Connect("mysql", "root:123456@tcp(localhost:3306)/driving_exam")
HandlerError(err, `sqlx.Connect("mysql", "root:123456@tcp(localhost:3306)/driving_exam")`)
defer db.Close()
selection := ""
values := make([]interface{}, 0)
for col,value := range argsMap{
selection += (" and "+col+"=?")
values = append(values, value)
}
selection = selection[4:]
sql := "select * from "+tableName+" where "+selection;
err = db.Select(dest, sql, values...)
if err != nil {
fmt.Println(err, `db.Select(&examScores, "select * from score where name=?;", name)`)
return
}
dbMutext.RUnlock()
return nil
}
/*将全员考试成绩单写入MySQL数据库*/
func WriteScore2Mysql(scoreMap map[string]int) {
//锁定为写模式,写入期间不允许读访问
dbMutext.Lock()
db, err := sqlx.Connect("mysql", "root:123456@tcp(localhost:3306)/driving_exam")
HandlerError(err, `sqlx.Connect("mysql", "root:123456@tcp(localhost:3306)/driving_exam")`)
defer db.Close()
for name, score := range scoreMap {
_, err := db.Exec("insert into score(name,score) values(?,?);", name, score)
HandlerError(err, `db.Exec("insert into score(name,score) values(?,?);", name, score)`)
fmt.Println("插入成功!")
}
fmt.Println("成绩录入完毕!")
//解锁数据库,开放查询
dbMutext.Unlock()
}
/*根据姓名从Redis缓存查询分数*/
func QueryScoreFromRedis(name string) (score int, err error) {
fmt.Println("QueryScoreFromRedis...")
conn, err := redis.Dial("tcp", "localhost:6379")
HandlerError(err, `redis.Dial("tcp", "local:6379")`)
defer conn.Close()
reply, e := conn.Do("get", name)
if reply != nil {
score, e = redis.Int(reply, e)
//fmt.Println("!!!!!!!!!!!!", score, e)
} else {
return 0, errors.New("未能从Redis中查到数据")
}
if err != nil {
fmt.Println(err, `conn.Do("get", name)或者redis.Int(reply, err)`)
return 0, e
}
return score, nil
}
/*将姓名与分数写入Redis缓存*/
func WriteScore2Redis(name string, score int) error {
conn, err := redis.Dial("tcp", "localhost:6379")
HandlerError(err, `redis.Dial("tcp", "local:6379")`)
defer conn.Close()
_, err = conn.Do("set", name, score)
fmt.Println("Redis写入成功!")
return err
}
考试业务封装
package main
import (
"time"
"fmt"
"utils"
)
var(
chNames = make(chan string, 100)
examers = make([]string, 0)
//信号量,只有5条车道
chLanes = make(chan int, 5)
//违纪者
chFouls = make(chan string, 100)
//考试成绩
scoreMap = make(map[string]int)
)
/*巡考逻辑*/
func Patrol() {
ticker := time.NewTicker(1 * time.Second)
for {
//fmt.Println("战狼正在巡考...")
select {
case name := <-chFouls:
fmt.Println(name, "考试违纪!!!!! ")
default:
fmt.Println("考场秩序良好")
}
<-ticker.C
}
}
/*考试逻辑*/
func TakeExam(name string) {
chLanes <- 123
fmt.Println(name, "正在考试...")
//记录参与考试的考生姓名
examers = append(examers, name)
//生成考试成绩
score := utils.GetRandomInt(0, 100)
//fmt.Println(score)
scoreMap[name] = score
if score < 10 {
score = 0
chFouls <- name
//fmt.Println(name, "考试违纪!!!", score)
}
//考试持续5秒
<-time.After(400 * time.Millisecond)
<-chLanes
//wg.Done()
}
/*二级缓存查询成绩*/
func QueryScore(name string) {
score, err := utils.QueryScoreFromRedis(name)
if err != nil {
fmt.Println(err)
//score, _ = utils.QueryScoreFromMysql(name)
scores := make([]utils.ExamScore, 0)
argsMap := make(map[string]interface{})
argsMap["name"] = name
//argsMap["score"] = 50
err = utils.QueryFromMysql("score", argsMap, &scores)
utils.HandlerError(err,`utils.QueryFromMysql("score", argsMap, &scores)`)
fmt.Println("Mysql成绩:", name, ":", scores[0].Score)
/*将数据写入Redis*/
utils.WriteScore2Redis(name, scores[0].Score)
} else {
fmt.Println("Redis成绩:", name, ":", score)
}
//wg.Done()
}
主调程序
package main
import (
"time"
"fmt"
"utils"
"sync"
)
/*
考场签到,名字丢入管道;
只有5个车道,最多供5个人同时考试;
考生按签到顺序依次考试,给予考生10%的违规几率;
每3秒钟巡视一次,发现违规的清出考场,否则输出考场时序良好;
所有考试者考完后,向MySQL数据库录入考试成绩;
成绩录入完毕通知考生,考生查阅自己的成绩;
当前目录下的成绩录入MySQL数据库,数据库允许一写多读;
再次查询成绩使用Redis缓存(二级缓存);
整理优化代码,提高复用程度;
*/
var (
wg sync.WaitGroup
)
/*主程序*/
func main() {
for i := 0; i < 20; i++ {
chNames <- utils.GetRandomName()
}
close(chNames)
/*巡考*/
go Patrol()
/*考生并发考试*/
for name := range chNames {
wg.Add(1)
go func(name string) {
TakeExam(name)
wg.Done()
}(name)
}
wg.Wait()
fmt.Println("考试完毕!")
/*录入成绩*/
wg.Add(1)
go func() {
utils.WriteScore2Mysql(scoreMap)
wg.Done()
}()
//故意给一个时间间隔,确保WriteScore2DB先抢到数据库的读写锁
<-time.After(1 * time.Second)
/*考生查询成绩*/
for _, name := range examers {
wg.Add(1)
go func(name string) {
QueryScore(name)
wg.Done()
}(name)
}
<-time.After(1 * time.Second)
for _, name := range examers {
wg.Add(1)
go func(name string) {
QueryScore(name)
wg.Done()
}(name)
}
wg.Wait()
fmt.Println("END")
}