Browse Source

event: mating 更新配种逻辑

Yi 4 months ago
parent
commit
3ff48abe66

+ 5 - 2
go.mod

@@ -3,7 +3,7 @@ module kpt-pasture
 go 1.17
 
 require (
-	gitee.com/xuyiping_admin/go_proto v0.0.0-20241113101816-de37e79ac6d7
+	gitee.com/xuyiping_admin/go_proto v0.0.0-20241114030036-e542467bac19
 	gitee.com/xuyiping_admin/pkg v0.0.0-20241108060137-caea58c59f5b
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/eko/gocache v1.1.0
@@ -23,6 +23,7 @@ require (
 	github.com/spf13/viper v1.16.0
 	github.com/stretchr/testify v1.8.4
 	github.com/xuri/excelize/v2 v2.8.0
+	github.com/yangxikun/gin-limit-by-key v0.0.0-20190512072151-520697354d5f
 	go.uber.org/dig v1.15.0
 	go.uber.org/zap v1.24.0
 	golang.org/x/sync v0.6.0
@@ -30,6 +31,8 @@ require (
 	gorm.io/gorm v1.25.2
 )
 
+require github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
+
 require (
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // indirect
@@ -97,7 +100,7 @@ require (
 	golang.org/x/net v0.22.0 // indirect
 	golang.org/x/sys v0.18.0 // indirect
 	golang.org/x/text v0.15.0 // indirect
-	golang.org/x/time v0.3.0 // indirect
+	golang.org/x/time v0.3.0
 	google.golang.org/appengine v1.6.8 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect
 	google.golang.org/grpc v1.64.0 // indirect

+ 6 - 30
go.sum

@@ -36,34 +36,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
 cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20241108021844-f28903a01f3a h1:bL18y2fYHv3Ml+7aika3uIDrCVD8BkwiKWy1+I+jLn4=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20241108021844-f28903a01f3a/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20241108073749-0d4badc06e82 h1:p0dlGbsRggDKk1kuWHW915CZpV3AbJFH/tPJNSF/nS0=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20241108073749-0d4badc06e82/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20241108091551-7dc4ce1f1dd1 h1:3RbPPejz79D12OMSuUgm1z5Ci15hGRfXigUE5lZkGz8=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20241108091551-7dc4ce1f1dd1/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20241111083814-39ea770c4d90 h1:AOE2HjFxTB+XYPdXd/mmp1xgf+Xp4peocezeHWMq0FU=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20241111083814-39ea770c4d90/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20241111092802-63701e01d5d9 h1:s6KZCEu5Cfr6v/FvjqmkSydLXYTVk3DiBSfhocqeiu4=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20241111092802-63701e01d5d9/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20241112021117-9e90bbaa3afe h1:wJIkTHfJPBs50mmt5VPqgB+WLle15RivFwqCByN7wL4=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20241112021117-9e90bbaa3afe/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20241112030243-a44c7ab062f8 h1:YQNkFjMAKyUxnC76Mpg6fRpSELZ+1m7nZlJz2rtL0r4=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20241112030243-a44c7ab062f8/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20241112031751-6a55598719ad h1:x6hPcphIrMJralgkpvxU0INiDx2AM1QjMTxcQYrmciQ=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20241112031751-6a55598719ad/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20241112033919-d1548b594232 h1:kERughSYPdXPKXcA/xXPXxxIlfCiV7R3sI7TcjWC438=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20241112033919-d1548b594232/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20241112070903-def17e1caa43 h1:DotHkoSwWIHuOdQBcZ0Vky6jXAbf0rOnsHChmr3mnqg=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20241112070903-def17e1caa43/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20241112090416-295c2aa69dab h1:72cBosC+SBsz5m42b47i9L+YdoL1mF8l9xd5uo81jx4=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20241112090416-295c2aa69dab/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20241113022812-b52528a61377 h1:Z6WoiWxa3mqk6Dxfv0jK/DDlcdqBI7wewp4w/ELEieo=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20241113022812-b52528a61377/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20241113084609-d7d3c9619f26 h1:VLwdNyG94ee7H8c4fQ7831vzP5DmU1Uuf0DYDTDGjYA=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20241113084609-d7d3c9619f26/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20241113101816-de37e79ac6d7 h1:BgZfIUaX6pdvF72FpX7dFb9KczzjzlD+DW69aYVc6F8=
-gitee.com/xuyiping_admin/go_proto v0.0.0-20241113101816-de37e79ac6d7/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241114030036-e542467bac19 h1:Sk47RofjO+bwK2sLbUuvMupUVna4yQmRmTloICbO6JU=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241114030036-e542467bac19/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
 gitee.com/xuyiping_admin/pkg v0.0.0-20241108060137-caea58c59f5b h1:w05MxH7yqveRlaRbxHhbif5YjPrJFodRPfOjYhXn7Zk=
 gitee.com/xuyiping_admin/pkg v0.0.0-20241108060137-caea58c59f5b/go.mod h1:8tF25X6pE9WkFCczlNAC0K2mrjwKvhhp02I7o0HtDxY=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
@@ -196,7 +170,6 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
-github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
 github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
 github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
 github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
@@ -464,6 +437,8 @@ github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnh
 github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
 github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
+github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
 github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
 github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
 github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
@@ -602,6 +577,8 @@ github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05 h1:qhbILQo1K3mphbwKh1vNm4
 github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
 github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2 h1:zzrxE1FKn5ryBNl9eKOeqQ58Y/Qpo3Q9QNxKHX5uzzQ=
 github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2/go.mod h1:hzfGeIUDq/j97IG+FhNqkowIyEcD88LrW6fyU3K3WqY=
+github.com/yangxikun/gin-limit-by-key v0.0.0-20190512072151-520697354d5f h1:ERcGMTmr8QfJ2KPgKGnyKG5QEEK+YxraUch0I0gN8uc=
+github.com/yangxikun/gin-limit-by-key v0.0.0-20190512072151-520697354d5f/go.mod h1:ysnqe7upAAVOSwxQZHAMPXbO80SFzg/ArkjnIJIcuGE=
 github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -628,7 +605,6 @@ go.uber.org/dig v1.15.0/go.mod h1:pKHs0wMynzL6brANhB2hLMro+zalv1osARTviTcqHLM=
 go.uber.org/goleak v0.10.0/go.mod h1:VCZuO8V8mFPlL0F5J5GK1rtHV3DrFcQ1R8ryq7FK0aI=
 go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
 go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
-go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
 go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=

+ 1 - 1
http/handler/event/event_breed.go

@@ -152,7 +152,7 @@ func MatingCreate(c *gin.Context) {
 	}
 
 	if err := valid.ValidateStruct(&req,
-		valid.Field(&req.CowId, valid.Required),
+		valid.Field(&req.CowIds, valid.Required),
 		valid.Field(&req.OperationId, valid.Required),
 		valid.Field(&req.FrozenSemenNumber, valid.Required),
 		valid.Field(&req.MatingAt, valid.Required),

+ 5 - 0
http/middleware/bus.go

@@ -2,6 +2,7 @@ package middleware
 
 import (
 	"kpt-pasture/dep"
+	"kpt-pasture/module/backend"
 
 	"github.com/gin-gonic/gin"
 )
@@ -18,3 +19,7 @@ func WithDependency(s *dep.HttpDependency) gin.HandlerFunc {
 func Dependency(c *gin.Context) *dep.HttpDependency {
 	return c.MustGet(KeyDep).(*dep.HttpDependency)
 }
+
+func BackendOperation(c *gin.Context) *backend.Hub {
+	return &(Dependency(c).StoreEventHub)
+}

+ 1 - 46
http/middleware/cors.go

@@ -1,16 +1,7 @@
 package middleware
 
 import (
-	"bytes"
-	"io"
-	"io/ioutil"
 	"net/http"
-	"runtime"
-	"strings"
-
-	"gitee.com/xuyiping_admin/pkg/logger/zaplog"
-
-	"go.uber.org/zap"
 
 	"github.com/gin-contrib/cors"
 	"github.com/gin-gonic/gin"
@@ -30,7 +21,7 @@ func CORS(configs ...cors.Config) gin.HandlerFunc {
 			//服务器支持的所有跨域请求的方法
 			c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE")
 			//允许跨域设置可以返回其他子段,可以自定义字段
-			c.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, X-CSRF-Token, Token,session")
+			c.Header("Access-Control-Allow-Headers", "Authorization, Content-Length")
 			// 允许浏览器(客户端)可以解析的头部 (重要)
 			c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers")
 			//设置缓存时间
@@ -44,42 +35,6 @@ func CORS(configs ...cors.Config) gin.HandlerFunc {
 			c.JSON(http.StatusOK, "ok!")
 			return
 		}
-
-		defer func() {
-			if err := recover(); err != nil {
-				body, _ := ioutil.ReadAll(c.Request.Body)
-
-				// 获取 panic 发生的位置
-				pc, file, line, ok := runtime.Caller(2)
-				funcName := ""
-				if ok {
-					fn := runtime.FuncForPC(pc).Name()
-					// 去除包路径,只保留函数名
-					/*funcName = filepath.Base(fn)
-					file = filepath.Base(file)*/
-					parts := strings.Split(fn, "/")
-					if len(parts) > 0 {
-						lastPart := parts[len(parts)-1]
-						parts = strings.Split(lastPart, ".")
-						if len(parts) > 0 {
-							funcName = parts[len(parts)-1]
-						}
-					}
-					file = strings.TrimPrefix(file, c.Request.Context().Value(gin.ContextKey).(string)+"/") // 尝试去除项目路径前缀(可选)
-				}
-				zaplog.Error("cors",
-					zap.Any("recover", err),
-					zap.Any("url", c.Request.URL),
-					zap.Any("method", method),
-					zap.Any("file", file),
-					zap.Any("line", line),
-					zap.Any("func", funcName),
-					zap.Any("request", string(body)),
-				)
-				c.Request.Body = io.NopCloser(bytes.NewBuffer(body))
-				c.JSON(http.StatusInternalServerError, gin.H{"err": "Internal Server Error"})
-			}
-		}()
 		c.Next()
 	}
 }

+ 0 - 11
http/middleware/hub.go

@@ -1,11 +0,0 @@
-package middleware
-
-import (
-	"kpt-pasture/module/backend"
-
-	"github.com/gin-gonic/gin"
-)
-
-func BackendOperation(c *gin.Context) *backend.Hub {
-	return &(Dependency(c).StoreEventHub)
-}

+ 37 - 6
http/middleware/log.go

@@ -2,11 +2,10 @@ package middleware
 
 import (
 	"bytes"
+	"io"
 	"io/ioutil"
-	"net"
 	"net/http"
-	"net/http/httputil"
-	"os"
+	"runtime"
 	"runtime/debug"
 	"strings"
 	"time"
@@ -56,7 +55,6 @@ func GinLogger() gin.HandlerFunc {
 			zap.String("query", c.Request.URL.RawQuery),
 			zap.String("ip", c.ClientIP()),
 			zap.String("user-agent", c.Request.UserAgent()),
-			//zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
 			zap.String("time", cost.String()),
 			zap.String("Request body", string(requestBody)),
 			zap.String("Response body", w.body.String()),
@@ -76,7 +74,7 @@ func GinRecovery(stack bool) gin.HandlerFunc {
 	return func(c *gin.Context) {
 		defer func() {
 			if err := recover(); err != nil {
-				// Check for a broken connection, as it is not really a
+				/*// Check for a broken connection, as it is not really a
 				// condition that warrants a panic stack trace.
 				var brokenPipe bool
 				if ne, ok := err.(*net.OpError); ok {
@@ -110,7 +108,40 @@ func GinRecovery(stack bool) gin.HandlerFunc {
 						zap.Any("error", err),
 						zap.String("request", string(httpRequest)),
 					)
-				}
+				}*/
+				defer func() {
+					if err = recover(); err != nil {
+						body, _ := ioutil.ReadAll(c.Request.Body)
+
+						// 获取 panic 发生的位置
+						pc, file, line, ok := runtime.Caller(2)
+						funcName := ""
+						if ok {
+							fn := runtime.FuncForPC(pc).Name()
+							// 去除包路径,只保留函数名
+							/*funcName = filepath.Base(fn)
+							file = filepath.Base(file)*/
+							parts := strings.Split(fn, "/")
+							if len(parts) > 0 {
+								lastPart := parts[len(parts)-1]
+								parts = strings.Split(lastPart, ".")
+								if len(parts) > 0 {
+									funcName = parts[len(parts)-1]
+								}
+							}
+							file = strings.TrimPrefix(file, c.Request.Context().Value(gin.ContextKey).(string)+"/") // 尝试去除项目路径前缀(可选)
+						}
+						zaplog.Error("cors",
+							zap.Any("recover", err),
+							zap.Any("url", c.Request.URL),
+							zap.Any("file", file),
+							zap.Any("line", line),
+							zap.Any("func", funcName),
+							zap.Any("request", string(body)),
+						)
+						c.Request.Body = io.NopCloser(bytes.NewBuffer(body))
+					}
+				}()
 				c.AbortWithStatus(http.StatusInternalServerError)
 			}
 		}()

+ 58 - 0
http/middleware/rate_limit.go

@@ -0,0 +1,58 @@
+package middleware
+
+import (
+	"fmt"
+	"time"
+
+	"gitee.com/xuyiping_admin/pkg/apierr"
+	"gitee.com/xuyiping_admin/pkg/xerr"
+	"github.com/gin-gonic/gin"
+	limit "github.com/yangxikun/gin-limit-by-key"
+	"golang.org/x/time/rate"
+)
+
+// RateLimit limit rate by client ip
+// limit 10 qps/client_ip and permit bursts of at most 10 tokens, and the limiter live time duration is 3 minutes
+// handle exceed rate limit request
+func RateLimit(opts ...*RateLimitOption) gin.HandlerFunc {
+	var opt *RateLimitOption
+	if len(opts) == 0 {
+		opt = defaultRateLimitOption
+	} else {
+		opt = opts[0]
+	}
+
+	return limit.NewRateLimiter(func(c *gin.Context) string {
+		return fmt.Sprintf("%s-%s", c.ClientIP(), authorization(c))
+	}, func(c *gin.Context) (*rate.Limiter, time.Duration) {
+		return rate.NewLimiter(rate.Every(opt.Interval), opt.Tokens), opt.LiveTime
+	}, func(c *gin.Context) {
+		apierr.AbortBadRequest(c, 429, xerr.Customf("request too many %s", c.ClientIP()))
+	})
+}
+
+type RateLimitOption struct {
+	Interval time.Duration
+	Tokens   int
+	LiveTime time.Duration
+}
+
+var defaultRateLimitOption = &RateLimitOption{
+	Interval: 100 * time.Millisecond,
+	Tokens:   10,
+	LiveTime: 3 * time.Minute,
+}
+
+var testRateLimitOption = &RateLimitOption{
+	Interval: 10 * time.Millisecond,
+	Tokens:   1000,
+	LiveTime: 1 * time.Minute,
+}
+
+func RateLimitOptionLow() *RateLimitOption {
+	return &RateLimitOption{
+		Interval: 1 * time.Second,
+		Tokens:   10,
+		LiveTime: 3 * time.Minute,
+	}
+}

+ 11 - 0
http/middleware/sso.go

@@ -37,6 +37,17 @@ func unauthorized(c *gin.Context) {
 	c.AbortWithStatusJSON(http.StatusUnauthorized, apierr.WithContext(c, commonPb.Error_UNAUTHORIZED))
 }
 
+func authorization(c *gin.Context) string {
+	if v := c.GetHeader("Authorization"); v != "" {
+		return v
+	}
+	if v := c.GetHeader("authorization"); v != "" {
+		return v
+	}
+
+	return ""
+}
+
 // RequireAdmin ...
 func RequireAdmin() gin.HandlerFunc {
 	return func(c *gin.Context) {

+ 4 - 3
model/cow.go

@@ -35,7 +35,6 @@ type Cow struct {
 	IsPregnant          pasturePb.IsShow_Kind          `json:"isPregnant"`
 	HealthStatus        pasturePb.HealthStatus_Kind    `json:"healthStatus"`
 	WeaningAt           int64                          `json:"weaningAt"`
-	CalvingAt           int64                          `json:"calvingAt"`
 	BirthAt             int64                          `json:"birthAti"`
 	AdmissionAt         int64                          `json:"admissionAt"`
 	FirstMatingAt       int64                          `json:"firstMatingAt"`
@@ -162,6 +161,7 @@ func NewCow(req *pasturePb.EventEnterRequest) *Cow {
 		FirstMatingAt:       int64(req.MatingAt),
 		LastMatingAt:        int64(req.MatingAt),
 		LastPregnantCheckAt: int64(req.PregnancyCheckAt),
+		AdmissionAt:         time.Now().Unix(),
 	}
 }
 
@@ -180,6 +180,7 @@ func NewCalfCow(motherId int64, fatherNumber string, calf *CalvingCalf) *Cow {
 		MotherNumber:    fmt.Sprintf("%d", motherId),
 		AdmissionStatus: pasturePb.AdmissionStatus_Admission,
 		IsPregnant:      pasturePb.IsShow_No,
+		AdmissionAt:     calf.BirthAt,
 	}
 }
 
@@ -210,10 +211,10 @@ func (c *Cow) GetDayAge() int32 {
 
 // GetCalvingAge 产后天数
 func (c *Cow) GetCalvingAge() int64 {
-	if c.CalvingAt <= 0 {
+	if c.LastMatingAt <= 0 {
 		return 0
 	}
-	return int64(math.Floor(float64(time.Now().Unix()-c.CalvingAt) / 86400))
+	return int64(math.Floor(float64(time.Now().Unix()-c.LastMatingAt) / 86400))
 }
 
 // GetDaysPregnant 怀孕天数

+ 7 - 8
model/event_mating.go

@@ -24,7 +24,6 @@ type EventMating struct {
 	MatingResultAt    int64                           `json:"matingResultAt"`
 	ExposeEstrusType  pasturePb.ExposeEstrusType_Kind `json:"exposeEstrusType"`
 	FrozenSemenNumber string                          `json:"frozenSemenNumber"`
-	FrozenSemenCount  int32                           `json:"frozenSemenCount"`
 	OperationId       int64                           `json:"operationId"`
 	OperationName     string                          `json:"operationName"`
 	MessageId         int64                           `json:"messageId"`
@@ -44,35 +43,35 @@ func NewEventMating(cow *Cow, planDay int64) *EventMating {
 		Lact:             cow.Lact,
 		CowType:          cow.CowType,
 		CowKind:          cow.CowKind,
-		CalvingAt:        cow.CalvingAt,
+		CalvingAt:        cow.LastMatingAt,
 		PlanDay:          planDay,
 		EndDay:           planDay,
-		MatingResult:     pasturePb.MatingResult_Invalid,
+		MatingResult:     pasturePb.MatingResult_Unknown,
 		ExposeEstrusType: pasturePb.ExposeEstrusType_Same_Time,
 		Status:           pasturePb.IsShow_No,
 	}
 }
 
 // NewEventMating2 自然发情的牛只
-func NewEventMating2(cow *Cow, req *pasturePb.EventMating) *EventMating {
+func NewEventMating2(cow *Cow, req *pasturePb.EventMating, currentUser *SystemUser) *EventMating {
 	return &EventMating{
 		CowId:             cow.Id,
 		Lact:              cow.Lact,
 		DayAge:            cow.GetDayAge(),
 		CowType:           cow.CowType,
 		CowKind:           cow.CowKind,
-		CalvingAt:         cow.CalvingAt,
+		CalvingAt:         cow.LastMatingAt,
 		PlanDay:           int64(req.MatingAt),
 		RealityDay:        int64(req.MatingAt),
 		EndDay:            int64(req.MatingAt),
-		MatingResult:      pasturePb.MatingResult_Invalid,
+		MatingResult:      pasturePb.MatingResult_Unknown,
 		ExposeEstrusType:  pasturePb.ExposeEstrusType_Natural_Estrus,
 		Status:            pasturePb.IsShow_Ok,
-		MatingTimes:       1,
 		OperationId:       int64(req.OperationId),
 		OperationName:     req.OperationName,
+		MessageId:         currentUser.Id,
+		MessageName:       currentUser.Name,
 		FrozenSemenNumber: req.FrozenSemenNumber,
-		FrozenSemenCount:  req.FrozenSemenCount,
 		Remarks:           req.Remarks,
 	}
 }

+ 9 - 17
model/frozen_semen_log.go

@@ -1,9 +1,11 @@
 package model
 
+import pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
+
 type FrozenSemenLog struct {
 	Id            int64  `json:"id"`
 	BullId        string `json:"bullId"`
-	CowId         int64  `json:"cowId"`
+	CowIds        string `json:"cowId"`
 	Quantity      int32  `json:"quantity"`
 	MatingAt      int64  `json:"matingAt"`
 	OperationId   int64  `json:"operationId"`
@@ -17,24 +19,14 @@ func (e *FrozenSemenLog) TableName() string {
 	return "frozen_semen_log"
 }
 
-type FrozenSemenItem struct {
-	CowId         int64  `json:"cowId"`
-	Quantity      int32  `json:"quantity"`
-	MatingAt      int64  `json:"matingAt"`
-	OperationId   int64  `json:"operationId"`
-	OperationName string `json:"operationName"`
-	BullId        string `json:"bullId"`
-	Remarks       string `json:"remarks"`
-}
-
-func NewEventFrozenSemenLog(req *FrozenSemenItem) *FrozenSemenLog {
+func NewEventFrozenSemenLog(req *pasturePb.EventMating) *FrozenSemenLog {
 	return &FrozenSemenLog{
-		BullId:        req.BullId,
-		CowId:         req.CowId,
-		OperationId:   req.OperationId,
+		BullId:        req.FrozenSemenNumber,
+		CowIds:        req.CowIds,
+		OperationId:   int64(req.OperationId),
 		OperationName: req.OperationName,
-		Quantity:      req.Quantity,
-		MatingAt:      req.MatingAt,
+		Quantity:      req.FrozenSemenCount,
+		MatingAt:      int64(req.MatingAt),
 		Remarks:       req.Remarks,
 	}
 }

+ 110 - 81
module/backend/event_breed.go

@@ -9,6 +9,9 @@ import (
 	"strings"
 	"time"
 
+	"gitee.com/xuyiping_admin/pkg/logger/zaplog"
+	"go.uber.org/zap"
+
 	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
 	"gitee.com/xuyiping_admin/pkg/xerr"
 	"gorm.io/gorm"
@@ -112,15 +115,14 @@ func (s *StoreEntry) CalvingCreate(ctx context.Context, req *pasturePb.EventCalv
 		if err = tx.Model(new(model.Cow)).Where("id = ?", cow.Id).
 			Updates(map[string]interface{}{
 				"calving_age":     0,
+				"mating_times":    0,
 				"lact":            cow.Lact + 1,
 				"breed_status":    pasturePb.BreedStatus_Calving,
 				"is_pregnant":     pasturePb.IsShow_No,
-				"calving_at":      int64(req.CalvingAt),
-				"last_calving_at": time.Now().Unix(),
+				"last_calving_at": int64(req.CalvingAt),
 			}).Error; err != nil {
 			return xerr.WithStack(err)
 		}
-
 		return nil
 	}); err != nil {
 		return xerr.WithStack(err)
@@ -320,83 +322,92 @@ func (s *StoreEntry) MatingList(ctx context.Context, req *pasturePb.SearchEventR
 }
 
 func (s *StoreEntry) MatingCreate(ctx context.Context, req *pasturePb.EventMating) error {
-	if len(req.CowId) <= 0 {
-		return xerr.Custom("请选择相关牛只")
-	}
-
-	cowList, err := s.ParseCowIds(ctx, req.CowId)
-	if err != nil {
-		return xerr.WithStack(err)
-	}
-
-	currentUser, err := s.GetCurrentSystemUser(ctx)
-	if err != nil {
-		return xerr.Customf("获取当前用户失败: %s", err.Error())
-	}
-
-	operationUser, err := s.GetSystemUserById(ctx, int64(req.OperationId))
+	eventCheckModel, err := s.MatingCreateCheck(ctx, req)
 	if err != nil {
 		return xerr.WithStack(err)
 	}
 
 	frozenSemen := &model.FrozenSemen{}
-	if err = s.DB.Where("bull_id = ?", req.FrozenSemenNumber).First(frozenSemen).Error; err != nil {
-		return xerr.WithStack(err)
+	if err = s.DB.Where("bull_id = ?", req.FrozenSemenNumber).
+		First(frozenSemen).Error; err != nil {
+		return xerr.Custom("未找到冻精信息")
 	}
 
+	// 更新复配的牛只
 	matingReMatchIds := make([]int64, 0)
+	// 新建配种信息的牛只
 	newMatingList := make([]*model.EventMating, 0)
+	// 更新配种信息牛只
 	updateMatingList := make([]int64, 0)
-	eventFrozenSemenLogList := make([]*model.FrozenSemenLog, 0)
+	// 所有牛只
 	cowIds := make([]int64, 0)
-	for _, cow := range cowList {
-		if cow.Sex != pasturePb.Genders_Female {
-			return xerr.Customf("牛只 %d 不是母牛", cow.Id)
+	// 需要更新配次的牛只
+	matingTimes := make([]int64, 0)
+
+	for _, cow := range eventCheckModel.CowList {
+		// 牛号&& 胎次 && 已配 && 配种结果未知
+		count1, err := s.GetEventMatingIsExIstByCowId(ctx, cow, pasturePb.IsShow_Ok)
+		if err != nil {
+			return xerr.WithStack(err)
 		}
-		// 获取当前牛只最后一次配种时间,判断是否为复配牛只
+
+		// 牛号&& 胎次 && 未配 && 配种结果未知
+		count2, err := s.GetEventMatingIsExIstByCowId(ctx, cow, pasturePb.IsShow_No)
+		if err != nil {
+			return xerr.WithStack(err)
+		}
+
+		// 1. 第一次配种,创建配种信息(自然发情牛只,未经过同期的牛只)
 		if cow.LastMatingAt <= 0 {
-			// 第一次配种,创建配种信息(自然发情牛只,未经过同期的牛只)
-			newMatingList = append(newMatingList, model.NewEventMating2(cow, req))
+			newMatingList = append(newMatingList, model.NewEventMating2(cow, req, eventCheckModel.CurrentUser))
+		}
+
+		lastMatingAt := time.Unix(cow.LastMatingAt, 0).Format(model.LayoutDate2)
+		currentMatingAt := time.Unix(int64(req.MatingAt), 0).Format(model.LayoutDate2)
+		lastAddOneDayAt := time.Unix(cow.LastMatingAt, 0).AddDate(0, 0, 1).Format(model.LayoutDate2)
+
+		//  2. 如何两次配种时间为连续两天之内&&有过一次配种记录,则更新为复配状态
+		if (currentMatingAt == lastMatingAt || currentMatingAt == lastAddOneDayAt) && count1 == 1 {
+			matingReMatchIds = append(matingReMatchIds, cow.Id)
 		} else {
-			// 如何两次配种时间为连续两天,则更新为复配状态
-			lastMatingAt := time.Unix(cow.LastMatingAt, 0).AddDate(0, 0, 1).Format(model.LayoutDate2)
-			currentMatingAt := time.Unix(int64(req.MatingAt), 0).Format(model.LayoutDate2)
-			if cow.LastMatingAt != int64(req.MatingAt) && lastMatingAt == currentMatingAt {
-				matingReMatchIds = append(matingReMatchIds, cow.Id)
-			} else {
-				updateMatingList = append(updateMatingList, cow.Id)
-			}
+			matingTimes = append(matingTimes, cow.Id)
+		}
+
+		// 3. 同期更新配种信息
+		if count2 == 1 {
+			updateMatingList = append(updateMatingList, cow.Id)
 		}
-		newFrozenSemenItem := &model.FrozenSemenItem{
-			CowId:         cow.Id,
-			Quantity:      req.FrozenSemenCount,
-			MatingAt:      int64(req.MatingAt),
-			OperationId:   operationUser.Id,
-			OperationName: operationUser.Name,
-			BullId:        req.FrozenSemenNumber,
-			Remarks:       req.Remarks,
-		}
-		itemFrozenSemenLog := model.NewEventFrozenSemenLog(newFrozenSemenItem)
-		eventFrozenSemenLogList = append(eventFrozenSemenLogList, itemFrozenSemenLog)
 		cowIds = append(cowIds, cow.Id)
 	}
 
+	zaplog.Info("MatingCreate",
+		zap.Any("cowIds", cowIds),
+		zap.Any("updateMatingList", updateMatingList),
+		zap.Any("newMatingList", newMatingList),
+		zap.Any("matingReMatchIds", matingReMatchIds),
+		zap.Any("req", req),
+	)
+
+	if len(cowIds) != len(updateMatingList)+len(matingReMatchIds)+len(newMatingList) {
+		return xerr.Custom("配种信息有误")
+	}
+
+	itemFrozenSemenLog := model.NewEventFrozenSemenLog(req)
 	if err = s.DB.Transaction(func(tx *gorm.DB) error {
+		// 更新配种事件数据(初配)
 		if len(updateMatingList) > 0 {
-			// 创建配种事件数据
 			if err = tx.Model(new(model.EventMating)).
 				Where("cow_id IN ?", updateMatingList).
 				Where("status = ?", pasturePb.IsShow_No).
 				Updates(map[string]interface{}{
 					"mating_result":       pasturePb.MatingResult_Unknown,
 					"status":              pasturePb.IsShow_Ok,
-					"reality_day":         time.Unix(int64(req.MatingAt), 0).Format(model.LayoutTime),
-					"frozen_semen_count":  1,
+					"reality_day":         int64(req.MatingAt),
 					"frozen_semen_number": req.FrozenSemenNumber,
-					"operation_id":        operationUser.Id,
-					"operation_name":      operationUser.Name,
-					"message_id":          currentUser.Id,
-					"message_name":        currentUser.Name,
+					"operation_id":        eventCheckModel.OperationUser.Id,
+					"operation_name":      eventCheckModel.OperationUser.Name,
+					"message_id":          eventCheckModel.CurrentUser.Id,
+					"message_name":        eventCheckModel.CurrentUser.Name,
 				}).Error; err != nil {
 				return xerr.WithStack(err)
 			}
@@ -405,7 +416,7 @@ func (s *StoreEntry) MatingCreate(ctx context.Context, req *pasturePb.EventMatin
 		// 更新已配种的牛只为复配状态
 		if len(matingReMatchIds) > 0 {
 			if err = tx.Model(new(model.EventMating)).
-				Where("id IN ?", matingReMatchIds).
+				Where("cow_id IN ?", matingReMatchIds).
 				Where("mating_result = ?", pasturePb.MatingResult_Unknown).
 				Where("status = ?", pasturePb.IsShow_Ok).
 				Update("mating_result", pasturePb.MatingResult_ReMatch).
@@ -422,40 +433,58 @@ func (s *StoreEntry) MatingCreate(ctx context.Context, req *pasturePb.EventMatin
 		}
 
 		// 创建冻精使用记录日志
-		if err = tx.Create(eventFrozenSemenLogList).Error; err != nil {
+		if err = tx.Create(itemFrozenSemenLog).Error; err != nil {
+			return xerr.WithStack(err)
+		}
+
+		// 如果有同期牛只,则修改为已结束状态
+		if err = tx.Model(new(model.CowSameTime)).
+			Where("cow_id IN ?", cowIds).
+			UpdateColumn("same_time_status", pasturePb.SameTimeStatus_End).Error; err != nil {
 			return xerr.WithStack(err)
 		}
+
 		// 更新牛只的繁殖状态为配种
-		if len(cowIds) > 0 {
-			// 如果有同期牛只,则修改为已结束状态
-			if err = tx.Table(new(model.CowSameTime).TableName()).
-				Where("cow_id IN ?", cowIds).
-				UpdateColumn("status", pasturePb.SameTimeStatus_End).Error; err != nil {
-			}
+		if err = tx.Model(new(model.Cow)).
+			Where("id IN ?", cowIds).
+			Where("admission_status = ?", pasturePb.AdmissionStatus_Admission).
+			Updates(map[string]interface{}{
+				"breed_status":     pasturePb.BreedStatus_Breeding,
+				"last_bull_number": req.FrozenSemenNumber,
+				"last_mating_at":   int64(req.MatingAt),
+			}).Error; err != nil {
+			return xerr.WithStack(err)
+		}
 
-			if err = tx.Model(new(model.Cow)).
-				Where("id IN ?", cowIds).
-				Where("admission_status = ?", pasturePb.AdmissionStatus_Admission).
-				Updates(map[string]interface{}{
-					"breed_status":     pasturePb.BreedStatus_Breeding,
-					"last_bull_number": req.FrozenSemenNumber,
-					"last_mating_at":   int64(req.MatingAt),
-				}).Error; err != nil {
-				return xerr.WithStack(err)
-			}
+		// 减去精液的数量
+		if err = tx.Model(frozenSemen).
+			Where("id = ?", frozenSemen.Id).
+			Where("quantity > 0").
+			UpdateColumn("quantity",
+				gorm.Expr("quantity - ?", req.FrozenSemenCount),
+			).Error; err != nil {
+			return xerr.WithStack(err)
+		}
 
-			// 减去精液的数量
-			if err = tx.Model(new(model.FrozenSemen)).
-				Where("id = ?", frozenSemen.Id).
-				Where("quantity > 0").
-				UpdateColumn("quantity",
-					gorm.Expr("quantity - ?", req.FrozenSemenCount),
-				).Error; err != nil {
-				return xerr.WithStack(err)
-			}
+		// 更新配次
+		if err = tx.Model(new(model.EventMating)).
+			Where("cow_id IN ?", matingTimes).
+			Where("reality_day = ?", req.MatingAt).
+			Updates(map[string]interface{}{
+				"mating_times": gorm.Expr("mating_times + 1"),
+			}).Error; err != nil {
+			return xerr.WithStack(err)
+		}
 
-			// todo 更新每头牛只的胎次配次
+		if err = tx.Model(new(model.Cow)).
+			Where("id IN ?", matingTimes).
+			Where("admission_status = ?", pasturePb.AdmissionStatus_Admission).
+			Updates(map[string]interface{}{
+				"mating_times": gorm.Expr("mating_times + 1"),
+			}).Error; err != nil {
+			return xerr.WithStack(err)
 		}
+
 		return nil
 	}); err != nil {
 		return xerr.WithStack(err)

+ 47 - 0
module/backend/event_check.go

@@ -0,0 +1,47 @@
+package backend
+
+import (
+	"context"
+	"kpt-pasture/model"
+
+	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
+	"gitee.com/xuyiping_admin/pkg/xerr"
+)
+
+type EventCheckModel struct {
+	CowList       []*model.Cow
+	CurrentUser   *model.SystemUser
+	OperationUser *model.SystemUser
+}
+
+func (s *StoreEntry) MatingCreateCheck(ctx context.Context, req *pasturePb.EventMating) (*EventCheckModel, error) {
+	if len(req.CowIds) <= 0 {
+		return nil, xerr.Custom("请选择相关牛只")
+	}
+
+	cowList, err := s.ParseCowIds(ctx, req.CowIds)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	operationUser, err := s.GetSystemUserById(ctx, int64(req.OperationId))
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	currentUser, err := s.GetCurrentSystemUser(ctx)
+	if err != nil {
+		return nil, xerr.Customf("获取当前用户失败: %s", err.Error())
+	}
+
+	for _, cow := range cowList {
+		if cow.Sex != pasturePb.Genders_Female {
+			return nil, xerr.Customf("牛只: %d,不是母牛", cow.Id)
+		}
+	}
+	return &EventCheckModel{
+		CowList:       cowList,
+		CurrentUser:   currentUser,
+		OperationUser: operationUser,
+	}, nil
+}

+ 51 - 26
module/backend/sql.go

@@ -37,7 +37,7 @@ func (s *StoreEntry) GetCurrentSystemUser(ctx context.Context) (*model.SystemUse
 	}
 	// 根据用户token获取用户数据
 	systemUser := &model.SystemUser{Name: userName}
-	if err = s.DB.Where("name = ?", userName).
+	if err = s.DB.Model(new(model.SystemUser)).Where("name = ?", userName).
 		Where("is_show = ? and is_delete = ?", pasturePb.IsShow_Ok, pasturePb.IsShow_Ok).
 		First(systemUser).Error; err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
@@ -49,10 +49,8 @@ func (s *StoreEntry) GetCurrentSystemUser(ctx context.Context) (*model.SystemUse
 }
 
 func (s *StoreEntry) GetSystemUserById(ctx context.Context, userId int64) (*model.SystemUser, error) {
-	systemUser := &model.SystemUser{
-		Id: userId,
-	}
-	if err := s.DB.First(systemUser).Error; err != nil {
+	systemUser := &model.SystemUser{Id: userId}
+	if err := s.DB.Model(new(model.SystemUser)).First(systemUser).Error; err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			return nil, xerr.Customf("该系统用户数据不存在: %d", userId)
 		}
@@ -63,7 +61,9 @@ func (s *StoreEntry) GetSystemUserById(ctx context.Context, userId int64) (*mode
 
 func (s *StoreEntry) SystemDeptList(ctx context.Context) ([]*model.SystemDept, error) {
 	deptList := make([]*model.SystemDept, 0)
-	if err := s.DB.Where("is_delete = ?", pasturePb.IsShow_Ok).Find(&deptList).Error; err != nil {
+	if err := s.DB.Model(new(model.SystemDept)).
+		Where("is_delete = ?", pasturePb.IsShow_Ok).
+		Find(&deptList).Error; err != nil {
 		return nil, xerr.WithStack(err)
 	}
 	return deptList, nil
@@ -71,7 +71,7 @@ func (s *StoreEntry) SystemDeptList(ctx context.Context) ([]*model.SystemDept, e
 
 func (s *StoreEntry) GetPenById(ctx context.Context, penId int32) (*model.Pen, error) {
 	penData := &model.Pen{Id: penId}
-	if err := s.DB.First(penData).Error; err != nil {
+	if err := s.DB.Model(new(model.Pen)).First(penData).Error; err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			return nil, xerr.Customf("该栏舍数据不存在: %d", penId)
 		}
@@ -82,7 +82,9 @@ func (s *StoreEntry) GetPenById(ctx context.Context, penId int32) (*model.Pen, e
 
 func (s *StoreEntry) GetPenList(ctx context.Context) ([]*model.Pen, error) {
 	penList := make([]*model.Pen, 0)
-	if err := s.DB.Where("is_delete = ?", pasturePb.IsShow_Ok).Find(&penList).Error; err != nil {
+	if err := s.DB.Model(new(model.Pen)).
+		Where("is_delete = ?", pasturePb.IsShow_Ok).
+		Find(&penList).Error; err != nil {
 		return nil, xerr.WithStack(err)
 	}
 	return penList, nil
@@ -97,9 +99,12 @@ func (s *StoreEntry) PenMap(ctx context.Context) map[int32]*model.Pen {
 	return penMap
 }
 
-func (s *StoreEntry) GetCowList(ctx context.Context) ([]*model.Cow, error) {
+func (s *StoreEntry) GetCowList(ctx context.Context, cowIds []int64) ([]*model.Cow, error) {
 	cowList := make([]*model.Cow, 0)
-	if err := s.DB.Where("admission_status = ?", pasturePb.AdmissionStatus_Admission).Find(&cowList).Error; err != nil {
+	if err := s.DB.Model(new(model.Cow)).
+		Where("id IN ?", cowIds).
+		Where("admission_status = ?", pasturePb.AdmissionStatus_Admission).
+		Find(&cowList).Error; err != nil {
 		return nil, xerr.WithStack(err)
 	}
 	return cowList, nil
@@ -107,7 +112,7 @@ func (s *StoreEntry) GetCowList(ctx context.Context) ([]*model.Cow, error) {
 
 func (s *StoreEntry) GetCowInfoByCowId(ctx context.Context, cowId int64) (*model.Cow, error) {
 	cowData := &model.Cow{Id: cowId}
-	if err := s.DB.Where("admission_status = ?", pasturePb.AdmissionStatus_Admission).First(cowData).Error; err != nil {
+	if err := s.DB.Model(new(model.Cow)).Where("admission_status = ?", pasturePb.AdmissionStatus_Admission).First(cowData).Error; err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			return nil, xerr.Customf("该牛只数据不存在: %d", cowId)
 		}
@@ -131,7 +136,7 @@ func (s *StoreEntry) GetTransferReasonInfo(ctx context.Context, reasonId int64)
 	configTransferPenReasonData := &model.ConfigTransferPenReason{
 		Id: reasonId,
 	}
-	if err := s.DB.First(configTransferPenReasonData).Error; err != nil {
+	if err := s.DB.Model(new(model.ConfigTransferPenReason)).First(configTransferPenReasonData).Error; err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			return nil, xerr.Customf("该转群原因数据不存在: %d", reasonId)
 		}
@@ -142,7 +147,7 @@ func (s *StoreEntry) GetTransferReasonInfo(ctx context.Context, reasonId int64)
 
 func (s *StoreEntry) GetCowWeightByLastSecond(ctx context.Context, cowId, lastWeightAt int64) (*model.EventWeight, error) {
 	cowWeightData := &model.EventWeight{}
-	if err := s.DB.Where("cow_id = ?", cowId).Where("weight_at < ?", lastWeightAt).First(cowWeightData).Error; err != nil {
+	if err := s.DB.Model(new(model.EventWeight)).Where("cow_id = ?", cowId).Where("weight_at < ?", lastWeightAt).First(cowWeightData).Error; err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			return nil, xerr.Customf("该牛只体重数据不存在: %d", cowId)
 		}
@@ -153,7 +158,7 @@ func (s *StoreEntry) GetCowWeightByLastSecond(ctx context.Context, cowId, lastWe
 
 func (s *StoreEntry) GetDrugsById(ctx context.Context, id int64) (*model.Drugs, error) {
 	drugs := &model.Drugs{Id: id}
-	if err := s.DB.First(drugs).Error; err != nil {
+	if err := s.DB.Model(new(model.Drugs)).First(drugs).Error; err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			return nil, xerr.Customf("该药品数据不存在: %d", id)
 		}
@@ -164,7 +169,7 @@ func (s *StoreEntry) GetDrugsById(ctx context.Context, id int64) (*model.Drugs,
 
 func (s *StoreEntry) DiseaseTypeList(ctx context.Context) ([]*model.ConfigDiseaseType, error) {
 	diseaseTypeList := make([]*model.ConfigDiseaseType, 0)
-	if err := s.DB.Where("is_show = ?", pasturePb.IsShow_Ok).Find(&diseaseTypeList).Error; err != nil {
+	if err := s.DB.Model(new(model.ConfigDiseaseType)).Where("is_show = ?", pasturePb.IsShow_Ok).Find(&diseaseTypeList).Error; err != nil {
 		return nil, xerr.WithStack(err)
 	}
 	return diseaseTypeList, nil
@@ -172,7 +177,7 @@ func (s *StoreEntry) DiseaseTypeList(ctx context.Context) ([]*model.ConfigDiseas
 
 func (s *StoreEntry) GetDiseaseById(ctx context.Context, id int32) (*model.Disease, error) {
 	newDisease := &model.Disease{}
-	if err := s.DB.Where("id = ?", id).First(newDisease).Error; err != nil {
+	if err := s.DB.Model(new(model.Disease)).Where("id = ?", id).First(newDisease).Error; err != nil {
 		return nil, xerr.WithStack(err)
 	}
 	return newDisease, nil
@@ -180,7 +185,7 @@ func (s *StoreEntry) GetDiseaseById(ctx context.Context, id int32) (*model.Disea
 
 func (s *StoreEntry) DiseaseListByIds(ctx context.Context, ids []int32) ([]*model.Disease, error) {
 	diseaseList := make([]*model.Disease, 0)
-	if err := s.DB.Where("id IN ?", ids).Find(&diseaseList).Error; err != nil {
+	if err := s.DB.Model(new(model.Disease)).Where("id IN ?", ids).Find(&diseaseList).Error; err != nil {
 		return nil, xerr.WithStack(err)
 	}
 	return diseaseList, nil
@@ -188,7 +193,7 @@ func (s *StoreEntry) DiseaseListByIds(ctx context.Context, ids []int32) ([]*mode
 
 func (s *StoreEntry) GetPrescriptionById(ctx context.Context, id int32) (*model.Prescription, error) {
 	newPrescription := &model.Prescription{}
-	if err := s.DB.Where("id = ?", id).
+	if err := s.DB.Model(new(model.Prescription)).Where("id = ?", id).
 		Where("is_show = ?", pasturePb.IsShow_Ok).
 		First(&newPrescription).Error; err != nil {
 		return nil, xerr.WithStack(err)
@@ -198,7 +203,7 @@ func (s *StoreEntry) GetPrescriptionById(ctx context.Context, id int32) (*model.
 
 func (s *StoreEntry) PrescriptionDrugsByPrescriptionId(ctx context.Context, prescriptionId int32) ([]*model.PrescriptionDrugs, error) {
 	newPrescriptionDrugsList := make([]*model.PrescriptionDrugs, 0)
-	if err := s.DB.Where("prescription_id = ?", prescriptionId).
+	if err := s.DB.Model(new(model.PrescriptionDrugs)).Where("prescription_id = ?", prescriptionId).
 		Where("is_show = ?", pasturePb.IsShow_Ok).
 		Find(&newPrescriptionDrugsList).Error; err != nil {
 		return nil, xerr.WithStack(err)
@@ -208,7 +213,7 @@ func (s *StoreEntry) PrescriptionDrugsByPrescriptionId(ctx context.Context, pres
 
 func (s *StoreEntry) GetImmunizationById(ctx context.Context, id int64) (*model.ImmunizationPlan, error) {
 	immunizationPlan := &model.ImmunizationPlan{Id: id}
-	if err := s.DB.First(immunizationPlan).Error; err != nil {
+	if err := s.DB.Model(new(model.ImmunizationPlan)).First(immunizationPlan).Error; err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			return nil, xerr.Customf("该免疫计划不存在: %d", id)
 		}
@@ -219,7 +224,7 @@ func (s *StoreEntry) GetImmunizationById(ctx context.Context, id int64) (*model.
 
 func (s *StoreEntry) GetWorkOrderSubByWorkOrderId(ctx context.Context, workOrderId int64) ([]*model.WorkOrderSub, error) {
 	workOrderSubList := make([]*model.WorkOrderSub, 0)
-	if err := s.DB.Where("work_order_id = ?", workOrderId).Find(&workOrderSubList).Error; err != nil {
+	if err := s.DB.Model(new(model.WorkOrderSub)).Where("work_order_id = ?", workOrderId).Find(&workOrderSubList).Error; err != nil {
 		return nil, xerr.WithStack(err)
 	}
 	return workOrderSubList, nil
@@ -227,7 +232,7 @@ func (s *StoreEntry) GetWorkOrderSubByWorkOrderId(ctx context.Context, workOrder
 
 func (s *StoreEntry) GetSameTimeById(ctx context.Context, id int64) (*model.SameTime, error) {
 	sameTime := &model.SameTime{Id: id}
-	if err := s.DB.Where("is_show = ?", pasturePb.IsShow_Ok).First(sameTime).Error; err != nil {
+	if err := s.DB.Model(new(model.SameTime)).Where("is_show = ?", pasturePb.IsShow_Ok).First(sameTime).Error; err != nil {
 		return nil, xerr.WithStack(err)
 	}
 	return sameTime, nil
@@ -253,7 +258,10 @@ func (s *StoreEntry) GetSystemDeptListById(ctx context.Context, id int64) (*mode
 
 func (s *StoreEntry) GetSystemBasicByName(ctx context.Context, name string) (*model.SystemBasic, error) {
 	systemBasic := &model.SystemBasic{}
-	if err := s.DB.Where("name = ?", name).Where("is_show = ?", pasturePb.IsShow_Ok).First(systemBasic).Error; err != nil {
+	if err := s.DB.Model(new(model.SystemBasic)).
+		Where("name = ?", name).
+		Where("is_show = ?", pasturePb.IsShow_Ok).
+		First(systemBasic).Error; err != nil {
 		return nil, xerr.WithStack(err)
 	}
 	return systemBasic, nil
@@ -276,7 +284,10 @@ func (s *StoreEntry) UpdateCowPenId(ctx context.Context, cowId, penId int64) {
 
 func (s *StoreEntry) GetLastEventMating(ctx context.Context, cowId int64) (*model.EventMating, error) {
 	res := &model.EventMating{}
-	if err := s.DB.Where("cow_id = ?", cowId).Order("reality_day desc").First(res).Error; err != nil {
+	if err := s.DB.Model(new(model.EventMating)).
+		Where("cow_id = ?", cowId).
+		Order("reality_day desc").
+		First(res).Error; err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			return res, nil
 		} else {
@@ -288,7 +299,7 @@ func (s *StoreEntry) GetLastEventMating(ctx context.Context, cowId int64) (*mode
 
 func (s *StoreEntry) GetOutboundById(ctx context.Context, id int64) (*model.Outbound, error) {
 	res := &model.Outbound{}
-	if err := s.DB.Where("id = ?", id).First(res).Error; err != nil {
+	if err := s.DB.Model(new(model.Outbound)).Where("id = ?", id).First(res).Error; err != nil {
 		return nil, xerr.WithStack(err)
 	}
 	return res, nil
@@ -296,8 +307,22 @@ func (s *StoreEntry) GetOutboundById(ctx context.Context, id int64) (*model.Outb
 
 func (s *StoreEntry) GetOutboundLogsByOutboundId(ctx context.Context, id int64) ([]*model.OutboundLog, error) {
 	list := make([]*model.OutboundLog, 0)
-	if err := s.DB.Where("outbound_id = ?", id).Find(&list).Error; err != nil {
+	if err := s.DB.Model(new(model.OutboundLog)).Where("outbound_id = ?", id).Find(&list).Error; err != nil {
 		return nil, xerr.WithStack(err)
 	}
 	return list, nil
 }
+
+func (s *StoreEntry) GetEventMatingIsExIstByCowId(ctx context.Context, cow *model.Cow, status pasturePb.IsShow_Kind) (int64, error) {
+	count := int64(0)
+	if err := s.DB.Model(new(model.EventMating)).
+		Where("cow_id = ?", cow.Id).
+		Where("lact = ?", cow.Lact).
+		Where("status = ?", status).
+		Where("mating_result = ?", pasturePb.MatingResult_Unknown).
+		Order("id desc").
+		Count(&count).Error; err != nil {
+		return 0, xerr.WithStack(err)
+	}
+	return count, nil
+}