This commit is contained in:
zhuyasen 2022-09-22 22:25:26 +08:00
parent ae6aa5f301
commit f884f0faed
16 changed files with 742 additions and 70 deletions

View File

@ -3,7 +3,7 @@ package main
import (
"context"
"flag"
"fmt"
"strconv"
"time"
@ -12,13 +12,18 @@ import (
"github.com/zhufuyi/sponge/internal/server"
"github.com/zhufuyi/sponge/pkg/app"
"github.com/zhufuyi/sponge/pkg/logger"
"github.com/zhufuyi/sponge/pkg/tracer"
// grpc import start
"fmt"
"github.com/zhufuyi/sponge/pkg/registry"
"github.com/zhufuyi/sponge/pkg/registry/etcd"
"github.com/zhufuyi/sponge/pkg/tracer"
clientv3 "go.etcd.io/etcd/client/v3"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
// grpc import end
)
var (

View File

@ -6,12 +6,12 @@ app:
name: "userExample" # 服务名称
env: "dev" # 运行环境dev:开发环境prod:生产环境pre:预生产环境
version: "v0.0.0" # 版本
host: "127.0.0.1" # 主机ip或域名
host: "127.0.0.1" # 主机名称或ip
enableProfile: false # 是否开启性能分析功能true:开启false:关闭
enableMetrics: true # 是否开启指标采集true:开启false:关闭
enableLimit: false # 是否开启限流true:开启false:关闭
enableTracing: false # 是否开启链路跟踪true:开启false:关闭
enableRegistryDiscovery: true # 是否开启注册与发现true:开启false:关闭
enableRegistryDiscovery: false # 是否开启注册与发现true:开启false:关闭
# http 设置
@ -75,6 +75,11 @@ jaeger:
samplingRate: 1.0 # 采样率0~1之间0表示禁止采样大于等于1表示采样所有链路
# etcd配置
etcd:
addrs: ["192.168.3.37:2379"]
# limit配置
rateLimiter:
dimension: "path" # 限流维度支持path和ip两种默认是path
@ -85,8 +90,3 @@ rateLimiter:
# metrics配置
metrics:
port: 9082
# etcd配置
etcd:
addrs: ["192.168.3.37:2379"]

1
doc.go
View File

@ -1,4 +1,5 @@
/*
sponge 是一个微服务框架支持http和grpc及服务治理.
*/
package sponge

View File

@ -185,6 +185,40 @@ const docTemplate = `{
}
}
},
"/api/v1/userExamples/ids": {
"post": {
"description": "使用post请求根据id数组获取userExample列表",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"userExample"
],
"summary": "根据id数组获取userExample列表",
"parameters": [
{
"description": "id 数组",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handler.GetUserExamples1Request"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.Result"
}
}
}
}
},
"/health": {
"get": {
"description": "check health",
@ -283,6 +317,18 @@ const docTemplate = `{
}
}
},
"handler.GetUserExamples1Request": {
"type": "object",
"properties": {
"ids": {
"type": "array",
"minItems": 1,
"items": {
"type": "integer"
}
}
}
},
"handler.Params": {
"type": "object",
"properties": {

View File

@ -181,6 +181,40 @@
}
}
},
"/api/v1/userExamples/ids": {
"post": {
"description": "使用post请求根据id数组获取userExample列表",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"userExample"
],
"summary": "根据id数组获取userExample列表",
"parameters": [
{
"description": "id 数组",
"name": "data",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handler.GetUserExamples1Request"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handler.Result"
}
}
}
}
},
"/health": {
"get": {
"description": "check health",
@ -279,6 +313,18 @@
}
}
},
"handler.GetUserExamples1Request": {
"type": "object",
"properties": {
"ids": {
"type": "array",
"minItems": 1,
"items": {
"type": "integer"
}
}
}
},
"handler.Params": {
"type": "object",
"properties": {

View File

@ -41,6 +41,14 @@ definitions:
description: 手机号码,必须在前加'+86'
type: string
type: object
handler.GetUserExamples1Request:
properties:
ids:
items:
type: integer
minItems: 1
type: array
type: object
handler.Params:
properties:
columns:
@ -222,6 +230,28 @@ paths:
summary: 获取userExample列表
tags:
- userExample
/api/v1/userExamples/ids:
post:
consumes:
- application/json
description: 使用post请求根据id数组获取userExample列表
parameters:
- description: id 数组
in: body
name: data
required: true
schema:
$ref: '#/definitions/handler.GetUserExamples1Request'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/handler.Result'
summary: 根据id数组获取userExample列表
tags:
- userExample
/health:
get:
consumes:

3
go.mod
View File

@ -3,6 +3,7 @@ module github.com/zhufuyi/sponge
go 1.19
require (
github.com/DATA-DOG/go-sqlmock v1.5.0
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5
github.com/alicebob/miniredis/v2 v2.23.0
github.com/bojand/ghz v0.110.0
@ -15,7 +16,6 @@ require (
github.com/go-playground/validator/v10 v10.11.0
github.com/go-redis/redis/extra/redisotel v0.3.0
github.com/go-redis/redis/v8 v8.11.5
github.com/gogo/protobuf v1.3.2
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/golang/protobuf v1.5.2
github.com/golang/snappy v0.0.3
@ -95,6 +95,7 @@ require (
github.com/go-redis/redis/extra/rediscmd v0.2.0 // indirect
github.com/go-sql-driver/mysql v1.6.0 // indirect
github.com/goccy/go-json v0.9.7 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect

2
go.sum
View File

@ -58,6 +58,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/DataDog/datadog-go v4.8.3+incompatible h1:fNGaYSuObuQb5nzeTQqowRAd9bpDIRRV4/gUtIBjh8Q=
github.com/DataDog/datadog-go v4.8.3+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=

107
internal/cache/userExample_test.go vendored Normal file
View File

@ -0,0 +1,107 @@
package cache
import (
"testing"
"time"
"github.com/zhufuyi/sponge/internal/model"
"github.com/zhufuyi/sponge/pkg/gotest"
"github.com/zhufuyi/sponge/pkg/utils"
"github.com/stretchr/testify/assert"
)
func newUserExampleCache() *gotest.Cache {
record1 := &model.UserExample{}
record1.ID = 1
record2 := &model.UserExample{}
record2.ID = 2
testData := map[string]interface{}{
utils.Uint64ToStr(record1.ID): record1,
utils.Uint64ToStr(record2.ID): record2,
}
c := gotest.NewCache(testData)
c.ICache = NewUserExampleCache(c.RedisClient)
return c
}
func Test_userExampleCache_Set(t *testing.T) {
c := newUserExampleCache()
defer c.Close()
record := c.TestDataSlice[0].(*model.UserExample)
err := c.ICache.(UserExampleCache).Set(c.Ctx, record.ID, record, time.Hour)
if err != nil {
t.Fatal(err)
}
}
func Test_userExampleCache_Get(t *testing.T) {
c := newUserExampleCache()
defer c.Close()
record := c.TestDataSlice[0].(*model.UserExample)
err := c.ICache.(UserExampleCache).Set(c.Ctx, record.ID, record, time.Hour)
if err != nil {
t.Fatal(err)
}
got, err := c.ICache.(UserExampleCache).Get(c.Ctx, record.ID)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, record, got)
}
func Test_userExampleCache_MultiGet(t *testing.T) {
c := newUserExampleCache()
defer c.Close()
var testData []*model.UserExample
for _, data := range c.TestDataSlice {
testData = append(testData, data.(*model.UserExample))
}
err := c.ICache.(UserExampleCache).MultiSet(c.Ctx, testData, time.Hour)
if err != nil {
t.Fatal(err)
}
got, err := c.ICache.(UserExampleCache).MultiGet(c.Ctx, c.GetIDs())
if err != nil {
t.Fatal(err)
}
expected := c.GetTestData()
for k, v := range expected {
assert.Equal(t, got[k], v.(*model.UserExample))
}
}
func Test_userExampleCache_MultiSet(t *testing.T) {
c := newUserExampleCache()
defer c.Close()
var testData []*model.UserExample
for _, data := range c.TestDataSlice {
testData = append(testData, data.(*model.UserExample))
}
err := c.ICache.(UserExampleCache).MultiSet(c.Ctx, testData, time.Hour)
if err != nil {
t.Fatal(err)
}
}
func Test_userExampleCache_Del(t *testing.T) {
c := newUserExampleCache()
defer c.Close()
record := c.TestDataSlice[0].(*model.UserExample)
err := c.ICache.(UserExampleCache).Del(c.Ctx, record.ID)
if err != nil {
t.Fatal(err)
}
}

View File

@ -24,6 +24,7 @@ type UserExampleDao interface {
DeleteByID(ctx context.Context, id uint64) error
UpdateByID(ctx context.Context, table *model.UserExample) error
GetByID(ctx context.Context, id uint64) (*model.UserExample, error)
GetByIDs(ctx context.Context, ids []uint64) ([]*model.UserExample, error)
GetByColumns(ctx context.Context, params *query.Params) ([]*model.UserExample, int64, error)
}
@ -104,7 +105,7 @@ func (d *userExampleDao) UpdateByID(ctx context.Context, table *model.UserExampl
update["login_at"] = table.LoginAt
}
// delete the templates code end
err := d.db.WithContext(ctx).Model(table).Where("id = ?", table.ID).Updates(update).Error
err := d.db.WithContext(ctx).Model(table).Updates(update).Error
if err != nil {
return err
}
@ -160,62 +161,6 @@ func (d *userExampleDao) GetByID(ctx context.Context, id uint64) (*model.UserExa
return record, nil
}
// GetByColumns 根据分页和列信息筛选多条记录
// params 包括分页参数和查询参数
// 分页参数(必须):
// page: 页码从0开始
// size: 每页行数
// sort: 排序字段默认是id倒叙可以在字段前添加-号表示倒序,没有-号表示升序,多个字段用逗号分隔
//
// 查询参数(非必须):
//
// name: 列名
// exp: 表达式,有=、!=、>、>=、<、<=、like七种类型值为空时默认是=
// value: 列值
// logic: 表示逻辑类型,有&(and)、||(or)两种类型值为空时默认是and
//
// 示例: 查询年龄大于20的男性
//
// params = &query.Params{
// Page: 0,
// Size: 20,
// Columns: []query.Column{
// {
// serviceName: "age",
// Exp: ">",
// Value: 20,
// },
// {
// serviceName: "gender",
// Value: "男",
// },
// }
func (d *userExampleDao) GetByColumns(ctx context.Context, params *query.Params) ([]*model.UserExample, int64, error) {
query, args, err := params.ConvertToGormConditions()
if err != nil {
return nil, 0, err
}
var total int64
err = d.db.WithContext(ctx).Model(&model.UserExample{}).Where(query, args...).Count(&total).Error
if err != nil {
return nil, 0, err
}
if total == 0 {
return nil, total, nil
}
records := []*model.UserExample{}
order, limit, offset := params.ConvertToPage()
err = d.db.WithContext(ctx).Order(order).Limit(limit).Offset(offset).Where(query, args...).Find(&records).Error
if err != nil {
return nil, 0, err
}
return records, total, err
}
// GetByIDs 根据id批量获取
func (d *userExampleDao) GetByIDs(ctx context.Context, ids []uint64) ([]*model.UserExample, error) {
records := []*model.UserExample{}
@ -254,3 +199,61 @@ func (d *userExampleDao) GetByIDs(ctx context.Context, ids []uint64) ([]*model.U
return records, nil
}
// GetByColumns 根据分页和列信息筛选多条记录
// params 包括分页参数和查询参数
// 分页参数(必须):
// page: 页码从0开始
// size: 每页行数
// sort: 排序字段默认是id倒叙可以在字段前添加-号表示倒序,没有-号表示升序,多个字段用逗号分隔
//
// 查询参数(非必须):
//
// name: 列名
// exp: 表达式,有=、!=、>、>=、<、<=、like七种类型值为空时默认是=
// value: 列值
// logic: 表示逻辑类型,有&(and)、||(or)两种类型值为空时默认是and
//
// 示例: 查询年龄大于20的男性
//
// params = &query.Params{
// Page: 0,
// Size: 20,
// Columns: []query.Column{
// {
// serviceName: "age",
// Exp: ">",
// Value: 20,
// },
// {
// serviceName: "gender",
// Value: "男",
// },
// }
func (d *userExampleDao) GetByColumns(ctx context.Context, params *query.Params) ([]*model.UserExample, int64, error) {
queryStr, args, err := params.ConvertToGormConditions()
if err != nil {
return nil, 0, err
}
var total int64
if params.Sort != "ignore count" { // 忽略测试标记
err = d.db.WithContext(ctx).Model(&model.UserExample{}).Select([]string{"id"}).Where(queryStr, args...).Count(&total).Error
if err != nil {
return nil, 0, err
}
if total == 0 {
return nil, total, nil
}
}
records := []*model.UserExample{}
order, limit, offset := params.ConvertToPage()
err = d.db.WithContext(ctx).Order(order).Limit(limit).Offset(offset).Where(queryStr, args...).Find(&records).Error
if err != nil {
return nil, 0, err
}
return records, total, err
}

View File

@ -0,0 +1,158 @@
package dao
import (
"testing"
"time"
"github.com/zhufuyi/sponge/internal/cache"
"github.com/zhufuyi/sponge/internal/model"
"github.com/zhufuyi/sponge/pkg/gotest"
"github.com/zhufuyi/sponge/pkg/mysql/query"
"github.com/DATA-DOG/go-sqlmock"
"gorm.io/gorm"
)
func newUserExampleDao() *gotest.Dao {
testData := &model.UserExample{}
testData.ID = 1
testData.CreatedAt = time.Now()
testData.UpdatedAt = testData.CreatedAt
// 初始化mock cache
c := gotest.NewCache(map[string]interface{}{"no cache": testData}) // 为了测试mysql禁止缓存
c.ICache = cache.NewUserExampleCache(c.RedisClient)
// 初始化mock dao
d := gotest.NewDao(c, testData)
d.IDao = NewUserExampleDao(d.DB, c.ICache.(cache.UserExampleCache))
return d
}
func Test_userExampleDao_Create(t *testing.T) {
d := newUserExampleDao()
defer d.Close()
testData := d.TestData.(*model.UserExample)
d.SqlMock.ExpectBegin()
d.SqlMock.ExpectExec("INSERT INTO .*").
WithArgs(d.GetAnyArgs(testData)...).
WillReturnResult(sqlmock.NewResult(1, 1))
d.SqlMock.ExpectCommit()
err := d.IDao.(UserExampleDao).Create(d.Ctx, testData)
if err != nil {
t.Fatal(err)
}
}
func Test_userExampleDao_DeleteByID(t *testing.T) {
d := newUserExampleDao()
defer d.Close()
testData := d.TestData.(*model.UserExample)
testData.DeletedAt = gorm.DeletedAt{
Time: time.Now(),
Valid: false,
}
d.SqlMock.ExpectBegin()
d.SqlMock.ExpectExec("UPDATE .*").
WithArgs(d.AnyTime, testData.ID).
WillReturnResult(sqlmock.NewResult(int64(testData.ID), 1))
d.SqlMock.ExpectCommit()
err := d.IDao.(UserExampleDao).DeleteByID(d.Ctx, testData.ID)
if err != nil {
t.Fatal(err)
}
}
func Test_userExampleDao_UpdateByID(t *testing.T) {
d := newUserExampleDao()
defer d.Close()
testData := d.TestData.(*model.UserExample)
d.SqlMock.ExpectBegin()
d.SqlMock.ExpectExec("UPDATE .*").
WithArgs(d.AnyTime, testData.ID).
WillReturnResult(sqlmock.NewResult(1, 1))
d.SqlMock.ExpectCommit()
err := d.IDao.(UserExampleDao).UpdateByID(d.Ctx, testData)
if err != nil {
t.Fatal(err)
}
}
func Test_userExampleDao_GetByID(t *testing.T) {
d := newUserExampleDao()
defer d.Close()
testData := d.TestData.(*model.UserExample)
rows := sqlmock.NewRows([]string{"id", "created_at", "updated_at"}).
AddRow(testData.ID, testData.CreatedAt, testData.UpdatedAt)
d.SqlMock.ExpectQuery("SELECT .*").
WithArgs(testData.ID).
WillReturnRows(rows)
_, err := d.IDao.(UserExampleDao).GetByID(d.Ctx, testData.ID)
if err != nil {
t.Fatal(err)
}
err = d.SqlMock.ExpectationsWereMet()
if err != nil {
t.Fatal(err)
}
}
func Test_userExampleDao_GetByIDs(t *testing.T) {
d := newUserExampleDao()
defer d.Close()
testData := d.TestData.(*model.UserExample)
rows := sqlmock.NewRows([]string{"id", "created_at", "updated_at"}).
AddRow(testData.ID, testData.CreatedAt, testData.UpdatedAt)
d.SqlMock.ExpectQuery("SELECT .*").
WithArgs(testData.ID).
WillReturnRows(rows)
_, err := d.IDao.(UserExampleDao).GetByIDs(d.Ctx, []uint64{testData.ID})
if err != nil {
t.Fatal(err)
}
err = d.SqlMock.ExpectationsWereMet()
if err != nil {
t.Fatal(err)
}
}
func Test_userExampleDao_GetByColumns(t *testing.T) {
d := newUserExampleDao()
defer d.Close()
testData := d.TestData.(*model.UserExample)
rows := sqlmock.NewRows([]string{"id", "created_at", "updated_at"}).
AddRow(testData.ID, testData.CreatedAt, testData.UpdatedAt)
d.SqlMock.ExpectQuery("SELECT .*").WillReturnRows(rows)
_, _, err := d.IDao.(UserExampleDao).GetByColumns(d.Ctx, &query.Params{
Page: 0,
Size: 10,
Sort: "ignore count", // 忽略测试 select count(*)
})
if err != nil {
t.Fatal(err)
}
err = d.SqlMock.ExpectationsWereMet()
if err != nil {
t.Fatal(err)
}
}

View File

@ -24,6 +24,7 @@ type UserExampleHandler interface {
DeleteByID(c *gin.Context)
UpdateByID(c *gin.Context)
GetByID(c *gin.Context)
ListByIDs(c *gin.Context)
List(c *gin.Context)
}
@ -184,6 +185,43 @@ func (h *userExampleHandler) GetByID(c *gin.Context) {
response.Success(c, gin.H{"userExample": data})
}
// ListByIDs 根据id数组获取多条记录
// @Summary 根据id数组获取userExample列表
// @Description 使用post请求根据id数组获取userExample列表
// @Tags userExample
// @Param data body GetUserExamplesByIDsRequest true "id 数组"
// @Accept json
// @Produce json
// @Success 200 {object} Result{}
// @Router /api/v1/userExamples/ids [post]
func (h *userExampleHandler) ListByIDs(c *gin.Context) {
form := &GetUserExamplesByIDsRequest{}
err := c.ShouldBindJSON(form)
if err != nil {
logger.Warn("ShouldBindJSON error: ", logger.Err(err), utils.FieldRequestIDFromContext(c))
response.Error(c, ecode.InvalidParams)
return
}
userExamples, err := h.iDao.GetByIDs(c.Request.Context(), form.IDs)
if err != nil {
logger.Error("GetByIDs error", logger.Err(err), logger.Any("form", form), utils.FieldRequestIDFromContext(c))
response.Error(c, ecode.ErrListUserExample)
return
}
data, err := convertUserExamples(userExamples)
if err != nil {
logger.Error("Copy error", logger.Err(err), logger.Any("form", form), utils.FieldRequestIDFromContext(c))
response.Error(c, ecode.InternalServerError)
return
}
response.Success(c, gin.H{
"userExamples": data,
})
}
// List 通过post获取多条记录
// @Summary 获取userExample列表
// @Description 使用post请求获取userExample列表
@ -267,7 +305,12 @@ type GetUserExampleByIDRespond struct {
// delete the templates code end
// GetUserExamplesRequest query params
// GetUserExamplesByIDsRequest request form ids
type GetUserExamplesByIDsRequest struct {
IDs []uint64 `json:"ids" binding:"min=1"`
}
// GetUserExamplesRequest request form params
type GetUserExamplesRequest struct {
query.Params
}

View File

@ -0,0 +1,207 @@
package handler
import (
"net/http"
"testing"
"time"
"github.com/zhufuyi/sponge/internal/cache"
"github.com/zhufuyi/sponge/internal/dao"
"github.com/zhufuyi/sponge/internal/model"
"github.com/zhufuyi/sponge/pkg/gohttp"
"github.com/zhufuyi/sponge/pkg/gotest"
"github.com/zhufuyi/sponge/pkg/mysql/query"
"github.com/DATA-DOG/go-sqlmock"
"github.com/jinzhu/copier"
)
func newUserExampleHandler() *gotest.Handler {
// todo 补充测试字段信息
testData := &model.UserExample{}
testData.ID = 1
testData.CreatedAt = time.Now()
testData.UpdatedAt = testData.CreatedAt
// 初始化mock cache
c := gotest.NewCache(map[string]interface{}{"no cache": testData})
c.ICache = cache.NewUserExampleCache(c.RedisClient)
// 初始化mock dao
d := gotest.NewDao(c, testData)
d.IDao = dao.NewUserExampleDao(d.DB, c.ICache.(cache.UserExampleCache))
// 初始化mock handler
h := gotest.NewHandler(d, testData)
h.IHandler = &userExampleHandler{iDao: d.IDao.(dao.UserExampleDao)}
testFns := []gotest.RouterInfo{
{
FuncName: "Create",
Method: http.MethodPost,
Path: "/userExample",
HandlerFunc: h.IHandler.(UserExampleHandler).Create,
},
{
FuncName: "DeleteByID",
Method: http.MethodDelete,
Path: "/userExample/:id",
HandlerFunc: h.IHandler.(UserExampleHandler).DeleteByID,
},
{
FuncName: "UpdateByID",
Method: http.MethodPut,
Path: "/userExample/:id",
HandlerFunc: h.IHandler.(UserExampleHandler).UpdateByID,
},
{
FuncName: "GetByID",
Method: http.MethodGet,
Path: "/userExample/:id",
HandlerFunc: h.IHandler.(UserExampleHandler).GetByID,
},
{
FuncName: "ListByIDs",
Method: http.MethodPost,
Path: "/userExamples/ids",
HandlerFunc: h.IHandler.(UserExampleHandler).ListByIDs,
},
{
FuncName: "List",
Method: http.MethodPost,
Path: "/userExamples",
HandlerFunc: h.IHandler.(UserExampleHandler).List,
},
}
h.GoRunHttpServer(testFns)
return h
}
func Test_userExampleHandler_Create(t *testing.T) {
h := newUserExampleHandler()
defer h.Close()
testData := &CreateUserExampleRequest{}
_ = copier.Copy(testData, h.TestData.(*model.UserExample))
h.MockDao.SqlMock.ExpectBegin()
args := h.MockDao.GetAnyArgs(h.TestData)
h.MockDao.SqlMock.ExpectExec("INSERT INTO .*").
WithArgs(args[:len(args)-1]...). // 根据实际参数数量修改
WillReturnResult(sqlmock.NewResult(1, 1))
h.MockDao.SqlMock.ExpectCommit()
result := &gohttp.StdResult{}
err := gohttp.Post(result, h.GetRequestURL("Create"), testData)
if err != nil {
t.Fatal(err)
}
if result.Code != 0 {
t.Fatalf("%+v", result)
}
}
func Test_userExampleHandler_DeleteByID(t *testing.T) {
h := newUserExampleHandler()
defer h.Close()
testData := h.TestData.(*model.UserExample)
result := &gohttp.StdResult{}
err := gohttp.Delete(result, h.GetRequestURL("DeleteByID", testData.ID))
if err != nil {
t.Fatal(err)
}
if result.Code != 0 {
t.Fatalf("%+v", result)
}
}
func Test_userExampleHandler_UpdateByID(t *testing.T) {
h := newUserExampleHandler()
defer h.Close()
testData := &UpdateUserExampleByIDRequest{}
_ = copier.Copy(testData, h.TestData.(*model.UserExample))
h.MockDao.SqlMock.ExpectBegin()
h.MockDao.SqlMock.ExpectExec("UPDATE .*").
WithArgs(h.MockDao.AnyTime, testData.ID). // 根据测试数据数量调整
WillReturnResult(sqlmock.NewResult(int64(testData.ID), 1))
h.MockDao.SqlMock.ExpectCommit()
result := &gohttp.StdResult{}
err := gohttp.Put(result, h.GetRequestURL("UpdateByID", testData.ID), testData)
if err != nil {
t.Fatal(err)
}
if result.Code != 0 {
t.Fatalf("%+v", result)
}
}
func Test_userExampleHandler_GetByID(t *testing.T) {
h := newUserExampleHandler()
defer h.Close()
testData := h.TestData.(*model.UserExample)
rows := sqlmock.NewRows([]string{"id", "created_at", "updated_at"}).
AddRow(testData.ID, testData.CreatedAt, testData.UpdatedAt)
h.MockDao.SqlMock.ExpectQuery("SELECT .*").
WithArgs(testData.ID).
WillReturnRows(rows)
result := &gohttp.StdResult{}
err := gohttp.Get(result, h.GetRequestURL("GetByID", testData.ID))
if err != nil {
t.Fatal(err)
}
if result.Code != 0 {
t.Fatalf("%+v", result)
}
}
func Test_userExampleHandler_ListByIDs(t *testing.T) {
h := newUserExampleHandler()
defer h.Close()
testData := h.TestData.(*model.UserExample)
rows := sqlmock.NewRows([]string{"id", "created_at", "updated_at"}).
AddRow(testData.ID, testData.CreatedAt, testData.UpdatedAt)
h.MockDao.SqlMock.ExpectQuery("SELECT .*").WillReturnRows(rows)
result := &gohttp.StdResult{}
err := gohttp.Post(result, h.GetRequestURL("ListByIDs"), &GetUserExamplesByIDsRequest{IDs: []uint64{testData.ID}})
if err != nil {
t.Fatal(err)
}
if result.Code != 0 {
t.Fatalf("%+v", result)
}
}
func Test_userExampleHandler_List(t *testing.T) {
h := newUserExampleHandler()
defer h.Close()
testData := h.TestData.(*model.UserExample)
rows := sqlmock.NewRows([]string{"id", "created_at", "updated_at"}).
AddRow(testData.ID, testData.CreatedAt, testData.UpdatedAt)
h.MockDao.SqlMock.ExpectQuery("SELECT .*").WillReturnRows(rows)
result := &gohttp.StdResult{}
err := gohttp.Post(result, h.GetRequestURL("List"), &GetUserExamplesRequest{query.Params{
Page: 0,
Size: 10,
Sort: "ignore count", // 忽略测试 select count(*)
}})
if err != nil {
t.Fatal(err)
}
if result.Code != 0 {
t.Fatalf("%+v", result)
}
}

View File

@ -18,5 +18,6 @@ func userExampleRouter(group *gin.RouterGroup, h handler.UserExampleHandler) {
group.DELETE("/userExample/:id", h.DeleteByID)
group.PUT("/userExample/:id", h.UpdateByID)
group.GET("/userExample/:id", h.GetByID)
group.POST("/userExamples/ids", h.ListByIDs)
group.POST("/userExamples", h.List) // 通过post任意列组合查询
}

View File

@ -15,7 +15,7 @@ type Limiter struct {
qpsLimiter sync.Map
}
// NewLimiter instantiation
// NewLimiter instantiated limiter
func NewLimiter() *Limiter {
return &Limiter{}
}

View File

@ -1,6 +1,10 @@
package utils
import "os"
import (
"fmt"
"net"
"os"
)
// GetHostname 获取主机名
func GetHostname() string {
@ -10,3 +14,21 @@ func GetHostname() string {
}
return name
}
// GetAvailablePort 获取可用端口
func GetAvailablePort() (int, error) {
address, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:0", "0.0.0.0"))
if err != nil {
return 0, err
}
listener, err := net.ListenTCP("tcp", address)
if err != nil {
return 0, err
}
port := listener.Addr().(*net.TCPAddr).Port
err = listener.Close()
return port, err
}