新人学习记和新人作业自测报告
学习内容范围和推荐文档
编程语言Go
新人作业采用Go语言进行编写,作为只会C++,在学校学过Python,略懂Java的选手,学这门语言感觉难度不大。
推荐一个学习链接给大家:
https://www.runoob.com/go/go-tutorial.html
个人感觉,Go语言是一门非常精简的代码,写法与Python有一定的相似性,引用SDK相关的库非常方便(在GoLand的支持下可以一键引用,比C++高到不知哪里去了)。但是它也有一些非常坑爹的特性(比如定义了变量就一定要用,否则会编译错误)
MySQL和Gorm
新人作业中,文章的存储和点赞数据的存储,需要采用MySQL数据库进行存储。由于新人作业采用Go语言进行编写,这意味着我们需要采用Go语言对数据库进行一系列的操作(这需要Gorm这一工具来实现)。
鄙人浅推一个MySQL的学习链接给大家:
https://www.runoob.com/sql/sql-tutorial.html
本作业中需要用到的MySQL知识包含以下内容
1,搭建MySQL环境
2,创建MySQL数据库,创建表(Table)
3,对某个表进行增,删,查(没有改)
在掌握MySQL后,可以来学习下Gorm。Gorm是Go语言对MySQL进行交互的框架。
鄙人浅推一个文档给大家GORM 基本介绍.pdf
下文为一个简单的Gorm样例代码。
package main import ( "fmt" "github.com/mitchellh/mapstructure" "gorm.io/driver/mysql" "gorm.io/gorm" ) //注意,这个Id,Name等元素,首字母必须大写 type User struct { Id int32 `gorm:"column:id;primary_key"` Name string `gorm:"column:name"` } func AddToDB(id int32, name string) { user := User{ Id: id, Name: name, } fmt.Println("user = ", user) DB.Table("test_table").Create(&user) } var DB *gorm.DB var err error func RemoveFromDB() { //支持移除多个 DB.Table("test_table").Where("id<=?", 102).Delete(nil) } func UpdateFromDB_new() { var need_modify = User{104, "modify2"} DB.Table("test_table").Where("id<=104").Updates(need_modify) } func main() { DB, err = gorm.Open(mysql.New(mysql.Config{ //root表示数据库账号,:和@中间是密码(这里没有设置) //tcp中间的地址和端口表示连接的数据库的ip地址和端口 //localtest_alpahinf表示当前选择的数据库名 //其余信息喂默认信息 DSN: "root:@tcp(127.0.0.1:3306)/localtest_alphainf?charset=utf8mb4&parseTime=True&loc=Local", }), ) //AddToDB(1234, "alphainf") //AddToDB(114, "beta") //AddToDB(104, "orzlyy") UpdateFromDB_new() mp := make(map[string]interface{}) DB.Table("test_table").Where("id = 114").Find(mp) now := User{} mapstructure.Decode(mp, &now) fmt.Println(now) }
Redis和GoRedis
作业要求中提到我们需要用Redis记录某文章的浏览量。
我们可以把Redis理解为一个轻量级的数据库。这个数据库可以方便地记录一些数据量较小的东西(比如说某一篇文章的访问量)。
GoRedis就是利用Go语言与Redis进行交互的框架。
鄙人推两个Redis的学习链接
https://www.runoob.com/redis/redis-commands.html
这里有一份挺详细的文档:Redis&&Goredis学习
此处奉上鄙人学习Redis/GoRedis时写下的测试代码
package main import ( "fmt" "github.com/go-redis/redis" ) // 声明一个全局的rdb变量 var rdb *redis.Client var err error // 初始化连接 func main() { //6379是本地Redis服务的默认端口号,默认也是没有密码的 rdb = redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "", // no password set DB: 0, // use default DB }) //ctx := context.Background() rdb.Set("score", 100, 0) rdb.Incr("score") fmt.Println(rdb.Get("alpha").Result()) //_, err = rdb.Ping().Result() //fmt.Println(err) }
KiteX/IDL/Thirft
这是个什么东西呢?
和学校里不同,公司里的项目通常需要多人协作来完成,这会带来以下问题:
1,模块与模块间存在调用和被调用的关系,开发者需要知道每个模块调用的规则
2,存在调用与被调用的模块之间,可能会采用不同的语言进行编写,但语言的不同不能导致交互的不能
3,模块与模块之间,为了有较好的可维护性,在一般参数外,需要额外传递一些参数(比如日志信息,错误信息等)。但如果每次都要手动地去写,既容易漏,又容易格式不规范。
因此,为解决上述问题,我们需要用KiteX来生成代码框架
这是IDL文件的编写教程:idl语法
这是KiteX的安装/使用教程:KiteX入门实践
在下文中,你可以看到本人新手作业的IDL文件,已包含注释
数据存储方案
在mysql中,新建两个表,用practice_xiefengze_articleinfo存储文章,用practice_xiefengze_articlelikeinfo存储文章点赞信息。在Redis中存储两类信息,分别为“文章当天浏览数”和“文章最近一次被浏览的日期”
MySQL
开两个MySQL的表,分别存储文章信息和文章点赞信息
表practice_xiefengze_articleinfo
CREATE TABLE `PRACTICE_XIEFENGZE_ARTICLEINFO`( `id` INT UNSIGNED AUTO_INCREMENT, `title` VARCHAR(100) NOT NULL, `content` VARCHAR(256), `author` VARCHAR(100), `create_time` VARCHAR(100), PRIMARY KEY (`id`) );
破防了,如果这个自增的id要能通过脚手架返回回来,则id必须叫做id,不能叫做什么article_id之类的东西,必须叫做id(大小写无所谓),(有bug!!!!浪费我1h)
表practice_xiefengze_articlelikeinfo
CREATE TABLE `PRACTICE_XIEFENGZE_ARTICLELIKEINFO`( `id` INT UNSIGNED AUTO_INCREMENT, `like_user_id` INT NOT NULL, `article_id` INT not null, `create_time` VARCHAR(100), PRIMARY KEY (`id`) );pri
Redis
存储文章浏览数,其key为articleid + view ,其中articleid表示该文章的id,浏览数用string存储
为支持存储当天浏览数,会存储上次被浏览的时间,其key为articleid+time,浏览时间以“年-月-日”的形式进行存储。
代码框架
代码框架含义详见下方注释,一些答疑见下方
namespace go ve.gmp.practice //记录文章信息的结构体,将与数据库表practice_xiefengze_articleinfo交互 //注意下方这个id必须叫做id,不能叫什么articleID之类的,否则会浪费你1h struct Article { 1: required i64 id, //记录文章ID,作为数据库主键 2: required string title, //记录文章标题 3: required string content, //记录文章内容 4: required string author, //记录作者姓名 5: required string createTime,//以字符串的形式记录下创建时间,格式为yyyy-mm-dd hh:mm:ss } //记录需创建文章信息的结构体 //注意:文章的ID将会自动递增,主键将由程序自动生成 //文章的创建时间将由程序自动生成 struct CreateArticleRequest { 1: required string title, //记录文章标题 2: required string content, //记录文章内容 3: required string author, //记录作者姓名 } //记录文章列表的结构体 struct ArticleResponse { 1: required list<Article> articles, //记录文章列表 } //记录文章点赞信息的结构体,将于数据库表practice_xiefengze_likeinfo交互 struct LikeInfo { 1: required i64 id, //记录点赞的ID,作为数据库主键 2: required i64 articleID, //记录被点赞文章的ID 3: required i64 likeUserID, //记录点赞人的ID 4: required string createTime,//以字符串的形式记录下点赞时间,格式与struct Article相同 } //某人给某文章点赞 //文章的id和点赞时间将由代码生成 struct LikeInfoRequest { 1: required i64 articleID, //文章的ID 2: required i64 likeUserID, //点赞的用户的ID } //获取某日点赞数和 struct GetLikesAndViewsResponse { 1: required i64 DayViews, //当日点击量 2: required i64 Likes, //总点赞数 } //创建文章的返回值 struct CreateArticleResponse {} //文章列表接口的返回值 struct GetArticleListRequest {} //请求一篇文章的结构体 //注意,该结构体将被两个服务所使用 struct GetArticleRequest{ 1: required i64 articleID, //只包含所请求的文章的ID } //给某文章点赞或取消点赞的返回值 struct LikeArticleResponse {} //支持的一系列服务 service ArticleService { CreateArticleResponse CreateArticle(1: CreateArticleRequest req) //创建文章的接口 ArticleResponse GetArticleList(1: GetArticleListRequest req) //获取文章列表的接口 Article GetArticle(1: GetArticleRequest req) //给出文章id,返回给定的文章信息,每访问一次,文章访问数将自动+1 LikeArticleResponse LikeArticle(1: LikeInfoRequest req) //给某文章点赞 LikeArticleResponse DislikeArticle(1: LikeInfoRequest req) //取消给某文章点赞 GetLikesAndViewsResponse GetLikesAndViews(1: GetArticleRequest req) //给出文章id,返回给定的文章信息 }
Q:为什么传入和传出的参数都是恰好一个结构体
这是代码规范的要求,若一个接口需要传入/传出数据,则必须通过结构体进行。
有一个特别的地方:若传入/传出的参数只有一个,甚至没有传入/传出参数,也需要对其封装结构体(可以封装空结构体)
结构体的命名规范:用于参数传入的结构体通常叫做xxxRequest,用于传出的结构体通常叫做xxxResponse
功能实现
1、实现创建文章接口,记录:标题、内容、作者、创建时间
将文章基本信息(标题,内容,作者)加入到一个结构体中,并将当前时间记录进结构体。最后将该结构体加入到数据库中,即可实现文章的保存。
加入数据库后,由于id属于自增变量,在article.id中将会返回该文章获得的id。(注意一定要叫id,否则无法直接返回自增的id)
新文章创建后,还在Redis中记录文章的访问次数和最近一次访问的日期。我们通过文章的id生成Redis中存储访问次数和日期的主键(详见下方代码第20和21行),并记录初始数据。
func (s *ArticleServiceImpl) CreateArticle(ctx context.Context, req *practice.CreateArticleRequest) (resp *practice.CreateArticleResponse, err error) { resp = practice.NewCreateArticleResponse() //将一篇新文章写入数据库 article := practice.Article{ Author: req.Author, Content: req.Content, Title: req.Title, CreateTime: time.Now().Format("2006-01-02 15:04:05"), } result := ArticleDB.Create(&article) if result.Error != nil { logs.CtxInfo(ctx, result.Error.Error()) return nil, result.Error } //这个返回值弄了我1h ArticleID := toString(article.Id) //viewTag和timeTag为两个字符串常量,用于生成不同的Key Rdb.Set(ArticleID+viewTag, "1", 0) Rdb.Set(ArticleID+timeTag, time.Now().Format("2006-01-02"), 0) return resp, nil }
2、实现获取文章列表接口,返回文章列表接口
直接与数据库交互,将整个表中的数据导出到list中即可实现
func (s *ArticleServiceImpl) GetArticleList(ctx context.Context, req *practice.GetArticleListRequest) (resp *practice.ArticleResponse, err error) { resp = practice.NewArticleResponse() result := ArticleDB.Find(&resp.Articles) if result.Error != nil { logs.CtxInfo(ctx, result.Error.Error()) return nil, result.Error } return resp, nil }
3、实现获取指定文章的详细信息接口,并使用redis记录当天浏览数
获取指定文章的方法,是基于给定的文章id,在数据库中进行查询所得到。
本代码中,若执行一次指定文章的查询,将会进行访问次数的自增。
func (s *ArticleServiceImpl) GetArticle(ctx context.Context, req *practice.GetArticleRequest) (resp *practice.Article, err error) { resp = practice.NewArticle() result := ArticleDB.Where("id=?", req.ArticleID).Find(resp) //fmt.Println(resp.Id, resp.Title, resp.Author) if result.Error != nil { return nil, result.Error } updateView(req.ArticleID, &ctx)//详见下文 Rdb.Incr(toString(req.ArticleID) + viewTag) return resp, nil }
上文中有一个updateView函数,该函数是用于检查该文章上次访问日期的函数。若上次访问该文章不是今天,则会将访问次数清零,并更新最近访问的日期。
func updateView(articleID int64, ctx *context.Context) { strID := toString(articleID) lastLikeTime, err := Rdb.Get(strID + timeTag).Result() if err != nil { logs.CtxInfo(*ctx, err.Error()) } nowTime := time.Now().Format("2006-01-02") //fmt.Println(lastLikeTime, nowTime) if lastLikeTime != nowTime { fmt.Println("enter") Rdb.Set(strID+timeTag, nowTime, 0) Rdb.Set(strID+viewTag, "0", 0) } }
4、实现点赞文章接口,记录:点赞人、点赞时间、点赞的文章;同时支持取消点赞
和创建文章具有一定的相似性,传入的参数只有点赞人的ID和被点赞文章的ID,点赞的时间也由本模块进行生成。
和创建文章不同的是,本模块会先检查该点赞人是否已经点赞了这篇文章,若被点赞过了就会报错,防止来自前端的攻击。
func (s *ArticleServiceImpl) LikeArticle(ctx context.Context, req *practice.LikeInfoRequest) (resp *practice.LikeArticleResponse, err error) { resp = practice.NewLikeArticleResponse() likeinfo := practice.LikeInfo{ LikeUserID: req.LikeUserID, ArticleID: req.ArticleID, CreateTime: time.Now().Format("2006-01-02 15:04:05"), } var lastLikeCnt int64 LikeDB.Where("like_user_id=? and article_id =?", req.LikeUserID, req.ArticleID).Count(&lastLikeCnt) if lastLikeCnt != 0 { err := errors.New("error: already like") logs.CtxInfo(ctx, err.Error()) fmt.Println(err.Error()) return nil, err } result := LikeDB.Create(&likeinfo) if result.Error != nil { logs.CtxInfo(ctx, result.Error.Error()) return nil, result.Error } return resp, nil }
取消点赞的代码和点赞的代码较为相似,同样有非法操作排除机制。
func (s *ArticleServiceImpl) DislikeArticle(ctx context.Context, req *practice.LikeInfoRequest) (resp *practice.LikeArticleResponse, err error) { resp = practice.NewLikeArticleResponse() var lastLikeCnt int64 LikeDB.Where("like_user_id=? and article_id =?", req.LikeUserID, req.ArticleID).Count(&lastLikeCnt) if lastLikeCnt == 0 { err := errors.New("error: no like") logs.CtxInfo(ctx, err.Error()) fmt.Println(err.Error()) return nil, err } result := LikeDB.Where("like_user_id=? and article_id =?", req.LikeUserID, req.ArticleID).Delete(nil) //result := LikeDB.Delete(&likeinfo) if result.Error != nil { logs.CtxInfo(ctx, result.Error.Error()) return nil, result.Error } return resp, nil }
5、实现获取指定文章的点赞数和当天浏览数接口
点赞数的查询,直接通过在表中查询符合条件的点赞记录的数量即可
当天浏览数,直接在Redis中查询出符合条件的内容即可(记得UpdateView)
func (s *ArticleServiceImpl) GetLikesAndViews(ctx context.Context, req *practice.GetArticleRequest) (resp *practice.GetLikesAndViewsResponse, err error) { resp = practice.NewGetLikesAndViewsResponse() //var likeCnt int64 LikeDB.Where("article_id =?", req.ArticleID).Count(&resp.Likes) updateView(req.ArticleID, &ctx) dayViewStr, err := Rdb.Get(toString(req.ArticleID) + viewTag).Result() if err != nil { logs.CtxInfo(ctx, err.Error()) } fmt.Sscanln(dayViewStr, &resp.DayViews) return resp, nil }
模块测试
创建文章测试
表中已存有若干篇文章
加入一篇文章,代码如下:
func testAddArticle() { ctx := context.Background() article := practice.CreateArticleRequest{ Author: "xiefengze", Content: "谢丰泽到此一游", Title: "到此一游", } cli.CreateArticle(ctx, &article) }
加入后情况如下:
返回文章测试
输出文章的测试代码如下:
func GetArticleListTest() { ctx := context.Background() resp, err := cli.GetArticleList(ctx, practice.NewGetArticleListRequest()) if err == nil { len := len(resp.Articles) for i := 0; i < len; i++ { fmt.Println(resp.Articles[i]) } } }
MySQL中存储的文章如上图所示,该函数输出如下图所示:
获取指定文章
测试代码如下:
func GetArticleTest(articleID int64) { ctx := context.Background() articleRequest := practice.GetArticleRequest{ ArticleID: articleID, } articleResponse, err := cli.GetArticle(ctx, &articleRequest) if err == nil { fmt.Println(articleResponse) } }
运行GetArticleTest(6),输出如下:
实现点赞/取消点赞
既有点赞数据如下:
增加一条点赞数据测试代码:
func LikeArticle(articleID int64, userID int64) { ctx := context.Background() likeArticle := practice.LikeInfoRequest{ ArticleID: articleID, LikeUserID: userID, } cli.LikeArticle(ctx, &likeArticle) }
运行LikeArticle(6, 101),得到:
取消点赞测试代码
func DislikeArticle(articleID int64, userID int64) { ctx := context.Background() likeArticle := practice.LikeInfoRequest{ ArticleID: articleID, LikeUserID: userID, } cli.DislikeArticle(ctx, &likeArticle) }
运行DislikeArticle(1000, 102)
获取指定文章的点赞数和当天浏览数
测试代码如下
func GetLikeAndViewTest(articleID int64) { ctx := context.Background() articleRequest := practice.GetArticleRequest{ ArticleID: articleID, } resp, err := cli.GetLikesAndViews(ctx, &articleRequest) // err.Error() if err == nil { fmt.Println(resp) } }
运行GetLikeAndViewTest(3),输出如下:
我们发现,以前的浏览数被自动清空了
我们进行一次访问,然后再输出试试
发现浏览的次数变为了1
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
2018-07-25 【bzoj4259】 残缺的字符串 FFT
2018-07-25 【bzoj4503】 两个串 FFT
2018-07-25 【LOJ 2542】【PKUWC2018】 随机游走(最值反演 + 树上期望dp)