新人学习记和新人作业自测报告

 

学习内容范围和推荐文档

编程语言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
 
posted @ 2022-07-25 14:12  AlphaInf  阅读(90)  评论(0编辑  收藏  举报