React组件的State

组件 state 必须能代表一个组件UI呈现的完整状态集,即组件的任何UI改变都可以从 state 的变化中反映出来;同时, state 还必须代表一个组件UI呈现的最小状态集,即 state 中的所有状态都用于反映组件UI的变化,没有任何多余的状态,也不应该存在通过其他状态计算而来的中间状态。

state vs 普通属性

首先,什么是普通属性?

我们的组件都是使用 ES6class 定义的,所以组件的属性其实也就是 class 的属性(更确切的说法是 class 实例化对象的属性,但因为JavaScript本质上是没有类的定义的, class 只不过是 ES6 提供的语法糖,所以这里模糊化类和对象的区别)。


smarteng 发布于 2022-6-30 13:38

golang 复制对象的正确做法

golang 复制对象的正确做法
需求
  实际运用中,传参是一对象指针,现在如何最简便地复制一对象?
实现
  :&* 先拿到值再指针?


smarteng 发布于 2022-6-29 14:08

基于DDD的golang实践案例

Check the github repo for the updated code:
https://github.com/victorsteven/food-app-server

领域驱动设计模式算是比较热门的话题了。
领域驱动设计(DDD)是一种软件开发方法,通过将实现与不断演变的模型相连接,简化了开发人员面临的复杂性。
本文不会重点去解释Golang中实现DDD的相关理念,而是作者根据自己的研究对DDD的理解。

什么是DDD?
以下是考虑使用DDD的原因:

  • 提供解决困难问题的原则和模式
  • 将复杂的设计基于领域模型
  • 在技术和领域专家之间发起创造性的协作,以迭代地完善解决领域问题的概念模型。

DDD包含4个层:

  1. Domain:这是定义应用程序的域和业务逻辑的地方
  2. Infrastructure:此层包含独立于我们的应用程序而存在的所有内容:外部库,数据库引擎等。
  3. Application:该层用作域和界面层之间的通道。将请求从接口层发送到域层,由域层处理请求并返回响应。
  4. Interface:该层包含与其他系统交互的所有内容,例如Web服务,RMI接口或Web应用程序以及批处理前端。

image

1

开始
我们将构建一个食物推荐API。
首先要做的是初始化依赖关系管理。我们将使用go.mod。在根目录(路径:food-app /)中,初始化go.mod:
go mod init food-app 项目的组织结构:
image
在该应用中,我们将使用postgres和redis数据库持久化数据。先定义一个含有连接信息的.env文件。
.env文件内容:

#Postgres
APP_ENV=local
API_PORT=8888
DB_HOST=127.0.0.1
DB_DRIVER=postgres
ACCESS_SECRET=98hbun98h
REFRESH_SECRET=786dfdbjhsb
DB_USER=steven
DB_PASSWORD=password
DB_NAME=food-app
DB_PORT=5432

#Mysql
#DB_HOST=127.0.0.1
#DB_DRIVER=mysql
#DB_USER=steven
#DB_PASSWORD=here
#DB_NAME=food-app
#DB_PORT=3306

#Postgres Test DB
TEST_DB_DRIVER=postgres
TEST_DB_HOST=127.0.0.1
TEST_DB_PASSWORD=password
TEST_DB_USER=steven
TEST_DB_NAME=food-app-test
TEST_DB_PORT=5432

#Redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=

该文件应位于根目录中(路径:food-app /)

2

Domain 层
我们将首先考虑领域。
该域具有几种模式。其中一些是:实体,值,存储库,服务等。
由于我们在此处构建的应用比较简单,因此我们仅考虑两种域模式:实体和存储库。
实体
这是我们定义“Schema”的地方。
例如,我们可以定义用户的结构。将该实体视为域的蓝图。

package entity

import (
"food-app/infrastructure/security"
"github.com/badoux/checkmail"
"html"
"strings"
"time"
)

type User struct {
ID        uint64     `gorm:"primary_key;auto_increment" json:"id"`
FirstName string     `gorm:"size:100;not null;" json:"first_name"`
LastName  string     `gorm:"size:100;not null;" json:"last_name"`
Email     string     `gorm:"size:100;not null;unique" json:"email"`
Password  string     `gorm:"size:100;not null;" json:"password"`
CreatedAt time.Time  `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"`
UpdatedAt time.Time  `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"`
DeletedAt *time.Time `json:"deleted_at,omitempty"`
}

type PublicUser struct {
ID        uint64 `gorm:"primary_key;auto_increment" json:"id"`
FirstName string `gorm:"size:100;not null;" json:"first_name"`
LastName  string `gorm:"size:100;not null;" json:"last_name"`
}

//BeforeSave is a gorm hook
func (u *User) BeforeSave() error {
hashPassword, err := security.Hash(u.Password)
if err != nil {
return err
}
u.Password = string(hashPassword)
return nil
}

type Users []User

//So that we dont expose the user's email address and password to the world
func (users Users) PublicUsers() []interface{} {
result := make([]interface{}, len(users))
for index, user := range users {
result[index] = user.PublicUser()
}
return result
}

//So that we dont expose the user's email address and password to the world
func (u *User) PublicUser() interface{} {
return &PublicUser{
ID:        u.ID,
FirstName: u.FirstName,
LastName:  u.LastName,
}
}

func (u *User) Prepare() {
u.FirstName = html.EscapeString(strings.TrimSpace(u.FirstName))
u.LastName = html.EscapeString(strings.TrimSpace(u.LastName))
u.Email = html.EscapeString(strings.TrimSpace(u.Email))
u.CreatedAt = time.Now()
u.UpdatedAt = time.Now()
}

func (u *User) Validate(action string) map[string]string {
var errorMessages = make(map[string]string)
var err error

switch strings.ToLower(action) {
case "update":
if u.Email == "" {
errorMessages["email_required"] = "email required"
}
if u.Email != "" {
if err = checkmail.ValidateFormat(u.Email); err != nil {
errorMessages["invalid_email"] = "email email"
}
}

case "login":
if u.Password == "" {
errorMessages["password_required"] = "password is required"
}
if u.Email == "" {
errorMessages["email_required"] = "email is required"
}
if u.Email != "" {
if err = checkmail.ValidateFormat(u.Email); err != nil {
errorMessages["invalid_email"] = "please provide a valid email"
}
}
case "forgotpassword":
if u.Email == "" {
errorMessages["email_required"] = "email required"
}
if u.Email != "" {
if err = checkmail.ValidateFormat(u.Email); err != nil {
errorMessages["invalid_email"] = "please provide a valid email"
}
}
default:
if u.FirstName == "" {
errorMessages["firstname_required"] = "first name is required"
}
if u.LastName == "" {
errorMessages["lastname_required"] = "last name is required"
}
if u.Password == "" {
errorMessages["password_required"] = "password is required"
}
if u.Password != "" &amp;&amp; len(u.Password) < 6 {
errorMessages["invalid_password"] = "password should be at least 6 characters"
}
if u.Email == "" {
errorMessages["email_required"] = "email is required"
}
if u.Email != "" {
if err = checkmail.ValidateFormat(u.Email); err != nil {
errorMessages["invalid_email"] = "please provide a valid email"
}
}
}
return errorMessages
}

在上面的文件中,定义了包含用户信息的用户结构,我们还添加了帮助程序功能,这些功能将验证和清理输入。调用了一种哈希方法,该方法用于哈希密码。这是在基础结构层中定义的。
定义 food 实体时采用相同的方法。
存储库
存储库定义了基础结构实现的方法的集合。这描绘了与给定数据库或第三方API交互的方法数量。
user 存储库如下所示:

package repository

import (
"food-app/domain/entity"
)

type UserRepository interface {
SaveUser(*entity.User) (*entity.User, map[string]string)
GetUser(uint64) (*entity.User, error)
GetUsers() ([]entity.User, error)
GetUserByEmailAndPassword(*entity.User) (*entity.User, map[string]string)
}

方法在接口中定义。这些方法稍后将在基础结构层中实现。
food 库几乎相同。

3

Infrastructure 层
该层实现存储库中定义的方法。这些方法与数据库或第三方API交互。本文中仅考虑数据库交互。
image
我们可以看到 user 存储库实现如下所示:

package persistence

import (
"errors"
"food-app/domain/entity"
"food-app/domain/repository"
"food-app/infrastructure/security"
"github.com/jinzhu/gorm"
"golang.org/x/crypto/bcrypt"
"strings"
)

type UserRepo struct {
db *gorm.DB
}

func NewUserRepository(db *gorm.DB) *UserRepo {
return &amp;UserRepo{db}
}
//UserRepo implements the repository.UserRepository interface
var _ repository.UserRepository = &amp;UserRepo{}

func (r *UserRepo) SaveUser(user *entity.User) (*entity.User, map[string]string) {
dbErr := map[string]string{}
err := r.db.Debug().Create(&amp;user).Error
if err != nil {
//If the email is already taken
if strings.Contains(err.Error(), "duplicate") || strings.Contains(err.Error(), "Duplicate") {
dbErr["email_taken"] = "email already taken"
return nil, dbErr
}
//any other db error
dbErr["db_error"] = "database error"
return nil, dbErr
}
return user, nil
}

func (r *UserRepo) GetUser(id uint64) (*entity.User, error) {
var user entity.User
err := r.db.Debug().Where("id = ?", id).Take(&amp;user).Error
if err != nil {
return nil, err
}
if gorm.IsRecordNotFoundError(err) {
return nil, errors.New("user not found")
}
return &amp;user, nil
}

func (r *UserRepo) GetUsers() ([]entity.User, error) {
var users []entity.User
err := r.db.Debug().Find(&amp;users).Error
if err != nil {
return nil, err
}
if gorm.IsRecordNotFoundError(err) {
return nil, errors.New("user not found")
}
return users, nil
}

func (r *UserRepo) GetUserByEmailAndPassword(u *entity.User) (*entity.User, map[string]string) {
var user entity.User
dbErr := map[string]string{}
err := r.db.Debug().Where("email = ?", u.Email).Take(&amp;user).Error
if gorm.IsRecordNotFoundError(err) {
dbErr["no_user"] = "user not found"
return nil, dbErr
}
if err != nil {
dbErr["db_error"] = "database error"
return nil, dbErr
}
//Verify the password
err = security.VerifyPassword(user.Password, u.Password)
if err != nil &amp;&amp; err == bcrypt.ErrMismatchedHashAndPassword {
dbErr["incorrect_password"] = "incorrect password"
return nil, dbErr
}
return &amp;user, nil
}

可以看到我们实现了存储库中定义的方法。使用实现了UserRepository接口的UserRepo结构可以做到这一点,如下行所示:

//UserRepo implements the repository.UserRepository interface
var _ repository.UserRepository = &amp;UserRepo{}

因此,我们通过创建包含以下内容的db.go文件来配置数据库:

package persistence

import (
"fmt"
"food-app/domain/entity"
"food-app/domain/repository"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/postgres"
)

type Repositories struct {
User repository.UserRepository
Food repository.FoodRepository
db   *gorm.DB
}

func NewRepositories(Dbdriver, DbUser, DbPassword, DbPort, DbHost, DbName string) (*Repositories, error) {
DBURL := fmt.Sprintf("host=%s port=%s user=%s dbname=%s sslmode=disable password=%s", DbHost, DbPort, DbUser, DbName, DbPassword)
db, err := gorm.Open(Dbdriver, DBURL)
if err != nil {
return nil, err
}
db.LogMode(true)

return &amp;Repositories{
User: NewUserRepository(db),
Food: NewFoodRepository(db),
db:   db,
}, nil
}

//closes the  database connection
func (s *Repositories) Close() error {
return s.db.Close()
}

//This migrate all tables
func (s *Repositories) Automigrate() error {
return s.db.AutoMigrate(&amp;entity.User{}, &amp;entity.Food{}).Error
}

在上面的文件中,我们定义了Repositories结构,该结构保存了应用中的所有存储库。我们有 user 和 food 库。该存储库还具有一个db实例,该实例被传递给 user 和 food(即NewUserRepository和NewFoodRepository)的“constructors”。

4

Application 层
我们已经在域中定义了API业务逻辑。该层连接 domain 和 interfaces 层。
以下是 user 的应用层:

package application

import (
"food-app/domain/entity"
"food-app/domain/repository"
)

type userApp struct {
us repository.UserRepository
}

//UserApp implements the UserAppInterface
var _ UserAppInterface = &amp;userApp{}

type UserAppInterface interface {
SaveUser(*entity.User) (*entity.User, map[string]string)
GetUsers() ([]entity.User, error)
GetUser(uint64) (*entity.User, error)
GetUserByEmailAndPassword(*entity.User) (*entity.User, map[string]string)
}

func (u *userApp) SaveUser(user *entity.User) (*entity.User, map[string]string) {
return u.us.SaveUser(user)
}

func (u *userApp) GetUser(userId uint64) (*entity.User, error) {
return u.us.GetUser(userId)
}

func (u *userApp) GetUsers() ([]entity.User, error) {
return u.us.GetUsers()
}

func (u *userApp) GetUserByEmailAndPassword(user *entity.User) (*entity.User, map[string]string) {
return u.us.GetUserByEmailAndPassword(user)
}

上面有保存和检索用户数据的方法。UserApp结构具有UserRepository接口,从而可以调用用户存储库方法。

5

Interfaces 层
接口是处理HTTP请求和响应的层。这里我们收到身份验证,与用户相关的内容和与食品相关的内容的传入请求。
image
用户处理
我们定义了保存用户,获取所有用户和获取特定用户的方法。这些可以在user_handler.go文件中找到。

package interfaces

import (
"food-app/application"
"food-app/domain/entity"
"food-app/infrastructure/auth"
"github.com/gin-gonic/gin"
"net/http"
"strconv"
)

//Users struct defines the dependencies that will be used
type Users struct {
us application.UserAppInterface
rd auth.AuthInterface
tk auth.TokenInterface
}

//Users constructor
func NewUsers(us application.UserAppInterface, rd auth.AuthInterface, tk auth.TokenInterface) *Users {
return &amp;Users{
us: us,
rd: rd,
tk: tk,
}
}

func (s *Users) SaveUser(c *gin.Context) {
var user entity.User
if err := c.ShouldBindJSON(&amp;user); err != nil {
c.JSON(http.StatusUnprocessableEntity, gin.H{
"invalid_json": "invalid json",
})
return
}
//validate the request:
validateErr := user.Validate("")
if len(validateErr) > 0 {
c.JSON(http.StatusUnprocessableEntity, validateErr)
return
}
newUser, err := s.us.SaveUser(&amp;user)
if err != nil {
c.JSON(http.StatusInternalServerError, err)
return
}
c.JSON(http.StatusCreated, newUser.PublicUser())
}

func (s *Users) GetUsers(c *gin.Context) {
users := entity.Users{} //customize user
var err error
//us, err = application.UserApp.GetUsers()
users, err = s.us.GetUsers()
if err != nil {
c.JSON(http.StatusInternalServerError, err.Error())
return
}
c.JSON(http.StatusOK, users.PublicUsers())
}

func (s *Users) GetUser(c *gin.Context) {
userId, err := strconv.ParseUint(c.Param("user_id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, err.Error())
return
}
user, err := s.us.GetUser(userId)
if err != nil {
c.JSON(http.StatusInternalServerError, err.Error())
return
}
c.JSON(http.StatusOK, user.PublicUser())
}

观察到返回用户时,我们仅返回一个公共用户(在实体中定义)。公共用户没有敏感的用户详细信息,例如电子邮件和密码。
授权处理
login_handler负责登录,注销和刷新令牌方法。在各自文件中定义的某些方法在此文件中被调用。最好在它们的文件路径之后在存储库中检出它们。

package interfaces

import (
"fmt"
"food-app/application"
"food-app/domain/entity"
"food-app/infrastructure/auth"
"github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
"net/http"
"os"
"strconv"
)

type Authenticate struct {
us application.UserAppInterface
rd auth.AuthInterface
tk auth.TokenInterface
}

//Authenticate constructor
func NewAuthenticate(uApp application.UserAppInterface, rd auth.AuthInterface, tk auth.TokenInterface) *Authenticate {
return &amp;Authenticate{
us: uApp,
rd: rd,
tk: tk,
}
}

func (au *Authenticate) Login(c *gin.Context) {
var user *entity.User
var tokenErr = map[string]string{}

if err := c.ShouldBindJSON(&amp;user); err != nil {
c.JSON(http.StatusUnprocessableEntity, "Invalid json provided")
return
}
//validate request:
validateUser := user.Validate("login")
if len(validateUser) > 0 {
c.JSON(http.StatusUnprocessableEntity, validateUser)
return
}
u, userErr := au.us.GetUserByEmailAndPassword(user)
if userErr != nil {
c.JSON(http.StatusInternalServerError, userErr)
return
}
ts, tErr := au.tk.CreateToken(u.ID)
if tErr != nil {
tokenErr["token_error"] = tErr.Error()
c.JSON(http.StatusUnprocessableEntity, tErr.Error())
return
}
saveErr := au.rd.CreateAuth(u.ID, ts)
if saveErr != nil {
c.JSON(http.StatusInternalServerError, saveErr.Error())
return
}
userData := make(map[string]interface{})
userData["access_token"] = ts.AccessToken
userData["refresh_token"] = ts.RefreshToken
userData["id"] = u.ID
userData["first_name"] = u.FirstName
userData["last_name"] = u.LastName

c.JSON(http.StatusOK, userData)
}

func (au *Authenticate) Logout(c *gin.Context) {
//check is the user is authenticated first
metadata, err := au.tk.ExtractTokenMetadata(c.Request)
if err != nil {
c.JSON(http.StatusUnauthorized, "Unauthorized")
return
}
//if the access token exist and it is still valid, then delete both the access token and the refresh token
deleteErr := au.rd.DeleteTokens(metadata)
if deleteErr != nil {
c.JSON(http.StatusUnauthorized, deleteErr.Error())
return
}
c.JSON(http.StatusOK, "Successfully logged out")
}

//Refresh is the function that uses the refresh_token to generate new pairs of refresh and access tokens.
func (au *Authenticate) Refresh(c *gin.Context) {
mapToken := map[string]string{}
if err := c.ShouldBindJSON(&amp;mapToken); err != nil {
c.JSON(http.StatusUnprocessableEntity, err.Error())
return
}
refreshToken := mapToken["refresh_token"]

//verify the token
token, err := jwt.Parse(refreshToken, func(token *jwt.Token) (interface{}, error) {
//Make sure that the token method conform to "SigningMethodHMAC"
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(os.Getenv("REFRESH_SECRET")), nil
})
//any error may be due to token expiration
if err != nil {
c.JSON(http.StatusUnauthorized, err.Error())
return
}
//is token valid?
if _, ok := token.Claims.(jwt.Claims); !ok &amp;&amp; !token.Valid {
c.JSON(http.StatusUnauthorized, err)
return
}
//Since token is valid, get the uuid:
claims, ok := token.Claims.(jwt.MapClaims)
if ok &amp;&amp; token.Valid {
refreshUuid, ok := claims["refresh_uuid"].(string) //convert the interface to string
if !ok {
c.JSON(http.StatusUnprocessableEntity, "Cannot get uuid")
return
}
userId, err := strconv.ParseUint(fmt.Sprintf("%.f", claims["user_id"]), 10, 64)
if err != nil {
c.JSON(http.StatusUnprocessableEntity, "Error occurred")
return
}
//Delete the previous Refresh Token
delErr := au.rd.DeleteRefresh(refreshUuid)
if delErr != nil { //if any goes wrong
c.JSON(http.StatusUnauthorized, "unauthorized")
return
}
//Create new pairs of refresh and access tokens
ts, createErr := au.tk.CreateToken(userId)
if createErr != nil {
c.JSON(http.StatusForbidden, createErr.Error())
return
}
//save the tokens metadata to redis
saveErr := au.rd.CreateAuth(userId, ts)
if saveErr != nil {
c.JSON(http.StatusForbidden, saveErr.Error())
return
}
tokens := map[string]string{
"access_token":  ts.AccessToken,
"refresh_token": ts.RefreshToken,
}
c.JSON(http.StatusCreated, tokens)
} else {
c.JSON(http.StatusUnauthorized, "refresh token expired")
}
}

6

运行程序
我们测试一下该应用。我们将连接路由,连接到数据库并启动应用程序。
在根目录中定义的main.go文件中完成。

package main

import (
"food-app/infrastructure/auth"
"food-app/infrastructure/persistence"
"food-app/interfaces"
"food-app/interfaces/fileupload"
"food-app/interfaces/middleware"
"github.com/gin-gonic/gin"
"github.com/joho/godotenv"
"log"
"os"
)

func init() {
//To load our environmental variables.
if err := godotenv.Load(); err != nil {
log.Println("no env gotten")
}
}

func main() {

dbdriver := os.Getenv("DB_DRIVER")
host := os.Getenv("DB_HOST")
password := os.Getenv("DB_PASSWORD")
user := os.Getenv("DB_USER")
dbname := os.Getenv("DB_NAME")
port := os.Getenv("DB_PORT")

//redis details
redis_host := os.Getenv("REDIS_HOST")
redis_port := os.Getenv("REDIS_PORT")
redis_password := os.Getenv("REDIS_PASSWORD")

services, err := persistence.NewRepositories(dbdriver, user, password, port, host, dbname)
if err != nil {
panic(err)
}
defer services.Close()
services.Automigrate()

redisService, err := auth.NewRedisDB(redis_host, redis_port, redis_password)
if err != nil {
log.Fatal(err)
}

tk := auth.NewToken()
fd := fileupload.NewFileUpload()

users := interfaces.NewUsers(services.User, redisService.Auth, tk)
foods := interfaces.NewFood(services.Food, services.User, fd, redisService.Auth, tk)
authenticate := interfaces.NewAuthenticate(services.User, redisService.Auth, tk)

r := gin.Default()
r.Use(middleware.CORSMiddleware()) //For CORS

//user routes
r.POST("/users", users.SaveUser)
r.GET("/users", users.GetUsers)
r.GET("/users/:user_id", users.GetUser)

//post routes
r.POST("/food", middleware.AuthMiddleware(), middleware.MaxSizeAllowed(8192000), foods.SaveFood)
r.PUT("/food/:food_id", middleware.AuthMiddleware(), middleware.MaxSizeAllowed(8192000), foods.UpdateFood)
r.GET("/food/:food_id", foods.GetFoodAndCreator)
r.DELETE("/food/:food_id", middleware.AuthMiddleware(), foods.DeleteFood)
r.GET("/food", foods.GetAllFood)

//authentication routes
r.POST("/login", authenticate.Login)
r.POST("/logout", authenticate.Logout)
r.POST("/refresh", authenticate.Refresh)

//Starting the application
app_port := os.Getenv("PORT") //using heroku host
if app_port == "" {
app_port = "8888" //localhost
}
log.Fatal(r.Run(":"+app_port))
}

其中的中间件也是定义在 interfaces 层。

package middleware

import (
"bytes"
"food-app/infrastructure/auth"
"github.com/gin-gonic/gin"
"io/ioutil"
"net/http"
)

func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
err := auth.TokenValid(c.Request)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"status": http.StatusUnauthorized,
"error":  err.Error(),
})
c.Abort()
return
}
c.Next()
}
}

func CORSMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, PATCH, DELETE")

if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
}
}

//Avoid a large file from loading into memory
//If the file size is greater than 8MB dont allow it to even load into memory and waste our time.
func MaxSizeAllowed(n int64) gin.HandlerFunc {
return func(c *gin.Context) {
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, n)
buff, errRead := c.GetRawData()
if errRead != nil {
//c.JSON(http.StatusRequestEntityTooLarge,"too large")
c.JSON(http.StatusRequestEntityTooLarge, gin.H{
"status":     http.StatusRequestEntityTooLarge,
"upload_err": "too large: upload an image less than 8MB",
})
c.Abort()
return
}
buf := bytes.NewBuffer(buff)
c.Request.Body = ioutil.NopCloser(buf)
}
}

我们现在可以使用以下命令运行该应用:
go run main.go


smarteng 发布于 2022-5-31 08:40

go语言string、int、int64互相转换

string到int

int,err:=strconv.Atoi(string)

string到int64

int64, err := strconv.ParseInt(string, 10, 64)

int到string

string:=strconv.Itoa(int)

int64到string

string:=strconv.FormatInt(int64,10)


smarteng 发布于 2022-4-1 11:27

golang-使用 go test 输出单元测试覆盖率

一、背景

单元测试覆盖率是衡量代码质量的一个重要指标,重要的代码文件覆盖率应该至少达到80%以上。Java 可以通过JaCoCo 统计覆盖率,那么go 项目如何进行代码覆盖率测试呢?

二、统计方式

1、生成覆盖率报告(go test 指令)

我们知道直接执行测试代码的指令: go test 指定文件名称 -run “测试方法”。
项目工程是如下架构的,可以通过这种方式生成覆盖率报告:
go test -mod=vendor -covermode=count -coverprofile=coverprofile.cov -run="^Test" -coverpkg=$(go list -mod=vendor ./… | grep -v “/test” | tr ‘\n’ ‘,’) ./…
生成结果:coverprofile.cov

指令简单说明:
mod=vendor: 加载依赖的方式:从本地vendor 目录加载。适用于服务器不能从外网下载依赖的情况
covermode: count: 统计代码访问次数;set: 统计代码是否被访问; atomic: 一般在并发工程中使用(?)
run: 正则方式指定需要运行的测试方法
coverpkg: 指定业务代码路径,多个用逗号隔开,详细说明在后面
./…:遍历当前目录下测试文件,包括子目录
关于为什么要使用coverpkg:
主要是因为我们的项目结构中,测试代码和业务代码是分开的,而不是放到同一个目录中。因此如果只指定测试方法,就无法识别到业务代码,来计算覆盖率了。
因此我们既需要指定测试代码路径(当然这里声明为 ./… 就可以了,只有_test 结尾的方法会自动作为测试方法),也需要指定业务代码路径(通过grep -v 和 tr 指令,最终将业务代码输出成 git/controller,git/database 的格式)

2、打开覆盖率报告

通过html 文件打开(推荐,能看到方法细节):
go tool cover -html=coverprofile.cov
在命令行直接查看:
go tool cover -func=coverprofile.cov

三、展望

当然我们可能会遇到一些特殊情况:比如不希望将init 方法也列入统计等等,这些后续再补充。


smarteng 发布于 2022-3-29 06:20

统计 Golang 项目的测试覆盖率

我们的项目主要基于 Golang 开发,公司要求统计测试覆盖率。所以,我们摸索了一下在 Golang 下如何统计整个项目得测试覆盖率


smarteng 发布于 2022-3-29 03:41

golang goquery selector(选择器) 使用

最近研究Go爬虫相关的知识,使用到goquery这个库比较多,尤其是对爬取到的HTML进行选择和查找匹配的内容时,goquery的选择器使用尤其多,而且还有很多不常用但又很有用的选择器,这里总结下,以供参考。
如果大家以前做过前端开发,对jquery不会陌生,goquery类似jquery,它是jquery的go版本实现。使用它,可以很方便的对HTML进行处理。

标签: goquery

smarteng 发布于 2021-12-9 09:29

golang中设置Host Header的小Tips

最近在使用go的http包的时候遇到的问题:

client := &amp;http.Client{}
req, _ := http.NewRequest("GET", url, nil)

//这样设置无效
res.Header.Set("Host", "xxx.xxx")

res, err := client.Do(req)

if err != nil {
return
}
defer res.Body.Close()

js, err = simplejson.NewFromReader(res.Body)
return

//设置Host需要这样处理
res.Host = "xxx.xxx"

smarteng 发布于 2021-11-24 11:00

Linux抓包工具Tcpdump使用示例教程

tcpdump 是一款强大的网络抓包工具,它使用 libpcap 库来抓取网络数据包,这个库在几乎在所有的 Linux/Unix 中都有。熟悉 tcpdump 的使用能够帮助你分析调试网络数据,本文将通过一个个具体的示例来介绍它在不同场景下的使用方法。不管你是系统管理员,程序员,云原生工程师还是 yaml 工程师,掌握 tcpdump 的使用都能让你如虎添翼,升职加薪。


smarteng 发布于 2021-11-24 09:57

腾讯云挂载初始化云硬盘

操作场景

本文档介绍如何初始化已挂载至云服务器的空云硬盘,在云硬盘上创建文件系统并向其中写入一个名为 qcloud.txt 的文件。

说明
关于初始化云硬盘的更多信息,请参考 初始化场景介绍


smarteng 发布于 2021-11-24 08:55

如何在 CentOS 上修改主机名

通常主机名在操作系统安装过程中设置或者在创建虚拟机的过程中动态指定的。
有很多原因,你需要去改变主机名。最常见的原因就是,当自动创建实例的时候,需要自动设置主机名。
这篇指南解释了如何在 CentOS 8 上修改主机名,而不需要重启系统。
想要修改 CentOS 系统的主机名,使用 hostnamectl set-hostname 命令加上新的主机名进行修改。

标签: hostname

smarteng 发布于 2021-11-22 12:25

博主

    blogger

    smarteng

    人生就流星,虽然转瞬即逝,但也有永恒。

日历

恰饭

标签