5.9 Go语言项目实战:驾考系统

Amiee7 · · 1801 次点击 · 开始浏览    置顶
这是一个创建于 的主题,其中的信息可能已经有所发展或是发生改变。
本篇文章IT兄弟连GO语言学院小美 给读者们分享一下5.9 Go语言项目实战:驾考系统 对GO语言感兴趣想要学习Golang开发技术的小伙伴就随小编来了解一下吧。 **项目需求** - 考场签到,名字丢入管道; - 只有5个车道,最多供5个人同时考试; - 考生按签到顺序依次考试,给予考生10%的违规几率; - 每3秒钟巡视一次,发现违规的清出考场,否则输出考场时序良好; - 所有考试者考完后,向MySQL数据库录入考试成绩; - 成绩录入完毕通知考生,考生查阅自己的成绩; - 当前目录下的成绩录入MySQL数据库,数据库允许一写多读; - 再次查询成绩使用Redis缓存(二级缓存); - 整理优化代码,提高复用程度; **主要技术栈** - 管道并发 - MySQL-Redis二级缓存 - 通用的数据库工具的封装 - 类库封装和复用 **错误处理工具** - 主要用于输出错误与错误爆发的场景,并暴力退出程序(当然是在DEBUG模式下) ``` 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 } ``` **起名系统** - 详见:[起名系统详解](https://blog.csdn.net/u010986776/article/details/84039389) ``` 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"` } ``` **数据库工具** 这份代码实现了二级缓存的核心功能,包括: - 将全员考试成绩单(姓名/成绩键值对map)写入MySQL数据库 - 根据姓名从Redis缓存查询分数 - 将姓名与分数写入Redis缓存 - 还实现了一个通用的MySQL表查询方法:根据任意表名和查询条件map进行查询,并将结果送入指定的指针地址中,从而具有较高的复用价值 ``` 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") } ``` 想要了解更多关于GO语言开发方面内容的小伙伴, 请关注IT兄弟连官网、公众号:GO语言研习社, IT兄弟连教育有专业的微软、谷歌讲师为您指导, 此外IT兄弟连老师精心推出的GO语言教程定能让你快速掌握GO语言从入门到精通开发实战技能。

有疑问加站长微信联系

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:812540095

1801 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传