实现ai
This commit is contained in:
@@ -261,6 +261,7 @@ func (a *API) setupRoutes() {
|
||||
feedGroup.GET("/recipes", a.recipeController.ListRecipes)
|
||||
feedGroup.POST("/recipes/generate-from-all-materials/:pig_type_id", a.recipeController.GenerateFromAllMaterials)
|
||||
feedGroup.POST("/recipes/generate-prioritized-stock/:pig_type_id", a.recipeController.GenerateRecipeWithPrioritizedStockRawMaterials)
|
||||
feedGroup.GET("/recipes/:id/ai-diagnose", a.recipeController.AIDiagnoseRecipe)
|
||||
}
|
||||
logger.Debug("饲料管理相关接口注册成功 (需要认证和审计)")
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package feed
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/controller"
|
||||
@@ -256,3 +257,48 @@ func (c *RecipeController) GenerateRecipeWithPrioritizedStockRawMaterials(ctx ec
|
||||
logger.Infof("%s: 配方生成成功, 新配方ID: %d", actionType, resp.ID)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeCreated, "配方生成成功", resp, actionType, "配方生成成功", resp)
|
||||
}
|
||||
|
||||
// AIDiagnoseRecipe godoc
|
||||
// @Summary AI点评配方
|
||||
// @Description 使用AI对指定配方进行点评,并针对目标猪类型给出建议。
|
||||
// @Tags 饲料管理-配方
|
||||
// @Security BearerAuth
|
||||
// @Produce json
|
||||
// @Param id path int true "配方ID"
|
||||
// @Param pig_type_id query int true "猪类型ID"
|
||||
// @Success 200 {object} controller.Response{data=dto.ReviewRecipeResponse} "业务码为200代表AI点评成功"
|
||||
// @Router /api/v1/feed/recipes/{id}/ai-diagnose [get]
|
||||
func (c *RecipeController) AIDiagnoseRecipe(ctx echo.Context) error {
|
||||
reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "AIDiagnoseRecipe")
|
||||
const actionType = "AI点评配方"
|
||||
fmt.Println("xxx")
|
||||
// 从路径参数中获取配方ID
|
||||
recipeIDStr := ctx.Param("id")
|
||||
recipeID, err := strconv.ParseUint(recipeIDStr, 10, 32)
|
||||
if err != nil {
|
||||
logger.Errorf("%s: 配方ID格式错误: %v, ID: %s", actionType, err, recipeIDStr)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的配方ID格式", actionType, "配方ID格式错误", recipeIDStr)
|
||||
}
|
||||
|
||||
// 从查询参数中获取猪类型ID
|
||||
pigTypeIDStr := ctx.QueryParam("pig_type_id")
|
||||
pigTypeID, err := strconv.ParseUint(pigTypeIDStr, 10, 32)
|
||||
if err != nil {
|
||||
logger.Errorf("%s: 猪类型ID格式错误: %v, ID: %s", actionType, err, pigTypeIDStr)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的猪类型ID格式", actionType, "猪类型ID格式错误", pigTypeIDStr)
|
||||
}
|
||||
|
||||
// 调用应用服务进行AI点评
|
||||
reviewResponse, err := c.recipeService.AIDiagnoseRecipe(reqCtx, uint32(recipeID), uint32(pigTypeID))
|
||||
if err != nil {
|
||||
logger.Errorf("%s: 服务层AI点评失败: %v, RecipeID: %d, PigTypeID: %d", actionType, err, recipeID, pigTypeID)
|
||||
if errors.Is(err, service.ErrRecipeNotFound) {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, err.Error(), actionType, "配方或猪类型不存在", map[string]uint32{"recipe_id": uint32(recipeID), "pig_type_id": uint32(pigTypeID)})
|
||||
}
|
||||
// 对于其他错误,统一返回内部服务器错误
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "AI点评失败: "+err.Error(), actionType, "服务层AI点评失败", map[string]uint32{"recipe_id": uint32(recipeID), "pig_type_id": uint32(pigTypeID)})
|
||||
}
|
||||
|
||||
logger.Infof("%s: AI点评成功, RecipeID: %d, PigTypeID: %d", actionType, recipeID, pigTypeID)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "AI点评成功", reviewResponse, actionType, "AI点评成功", reviewResponse)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package dto
|
||||
|
||||
import "git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
|
||||
// =============================================================================================================
|
||||
// 营养种类 (Nutrient) 相关 DTO
|
||||
// =============================================================================================================
|
||||
@@ -335,3 +337,14 @@ type GenerateRecipeResponse struct {
|
||||
Name string `json:"name"` // 新生成的配方名称
|
||||
Description string `json:"description"` // 新生成的配方描述
|
||||
}
|
||||
|
||||
// ReviewRecipeRequest 定义了点评配方的请求体
|
||||
type ReviewRecipeRequest struct {
|
||||
PigTypeID uint32 `json:"pig_type_id" binding:"required"` // 猪类型ID
|
||||
}
|
||||
|
||||
// ReviewRecipeResponse 定义了点评配方的响应体
|
||||
type ReviewRecipeResponse struct {
|
||||
ReviewMessage string `json:"review_message"` // 点评内容
|
||||
AIModel models.AIModel `json:"ai_model"` // 使用的 AI 模型
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ type RecipeService interface {
|
||||
GenerateRecipeWithAllRawMaterials(ctx context.Context, pigTypeID uint32) (*models.Recipe, error)
|
||||
// GenerateRecipeWithPrioritizedStockRawMaterials 生成新配方,优先使用有库存的原料
|
||||
GenerateRecipeWithPrioritizedStockRawMaterials(ctx context.Context, pigTypeID uint32) (*models.Recipe, error)
|
||||
// AIDiagnoseRecipe 智能诊断配方, 返回智能诊断结果和使用的AI模型
|
||||
AIDiagnoseRecipe(ctx context.Context, recipeID uint32, pigTypeID uint32) (*dto.ReviewRecipeResponse, error)
|
||||
}
|
||||
|
||||
// recipeServiceImpl 是 RecipeService 接口的实现
|
||||
@@ -175,3 +177,18 @@ func (s *recipeServiceImpl) ListRecipes(ctx context.Context, req *dto.ListRecipe
|
||||
|
||||
return dto.ConvertRecipeListToDTO(recipes, total, req.Page, req.PageSize), nil
|
||||
}
|
||||
|
||||
// AIDiagnoseRecipe 实现智能诊断配方的方法
|
||||
func (s *recipeServiceImpl) AIDiagnoseRecipe(ctx context.Context, recipeID uint32, pigTypeID uint32) (*dto.ReviewRecipeResponse, error) {
|
||||
serviceCtx := logs.AddFuncName(ctx, s.ctx, "AIDiagnoseRecipe")
|
||||
|
||||
reviewMessage, aiModel, err := s.recipeSvc.AIDiagnoseRecipe(serviceCtx, recipeID, pigTypeID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("AI 诊断配方失败: %w", err)
|
||||
}
|
||||
|
||||
return &dto.ReviewRecipeResponse{
|
||||
ReviewMessage: reviewMessage,
|
||||
AIModel: aiModel,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/plan"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/recipe"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/task"
|
||||
infra_ai "git.huangwc.com/pig/pig-farm-controller/internal/infra/ai"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/config"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/database"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||
@@ -34,6 +35,7 @@ type Infrastructure struct {
|
||||
storage database.Storage
|
||||
repos *Repositories
|
||||
lora *LoraComponents
|
||||
aiManager infra_ai.AI
|
||||
tokenGenerator token.Generator
|
||||
}
|
||||
|
||||
@@ -53,10 +55,17 @@ func initInfrastructure(ctx context.Context, cfg *config.Config) (*Infrastructur
|
||||
|
||||
tokenGenerator := token.NewTokenGenerator([]byte(cfg.App.JWTSecret))
|
||||
|
||||
// 初始化 AI
|
||||
aiManager, err := infra_ai.NewGeminiAI(logs.AddCompName(ctx, "GeminiAI"), &cfg.AI)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("初始化 AI 管理器失败: %w", err)
|
||||
}
|
||||
|
||||
return &Infrastructure{
|
||||
storage: storage,
|
||||
repos: repos,
|
||||
lora: lora,
|
||||
aiManager: aiManager,
|
||||
tokenGenerator: tokenGenerator,
|
||||
}, nil
|
||||
}
|
||||
@@ -238,6 +247,8 @@ func initDomainServices(ctx context.Context, cfg *config.Config, infra *Infrastr
|
||||
pigTypeService,
|
||||
recipeCoreService,
|
||||
recipeGenerateManager,
|
||||
infra.repos.recipeRepo,
|
||||
infra.aiManager,
|
||||
)
|
||||
|
||||
return &DomainServices{
|
||||
|
||||
@@ -2,8 +2,11 @@ package recipe
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/ai"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
||||
@@ -22,6 +25,8 @@ type Service interface {
|
||||
GenerateRecipeWithAllRawMaterials(ctx context.Context, pigTypeID uint32) (*models.Recipe, error)
|
||||
// GenerateRecipeWithPrioritizedStockRawMaterials 生成新配方,优先使用有库存的原料
|
||||
GenerateRecipeWithPrioritizedStockRawMaterials(ctx context.Context, pigTypeID uint32) (*models.Recipe, error)
|
||||
// AIDiagnoseRecipe 智能诊断配方, 返回智能诊断结果
|
||||
AIDiagnoseRecipe(ctx context.Context, recipeID uint32, pigTypeID uint32) (string, models.AIModel, error)
|
||||
}
|
||||
|
||||
// recipeServiceImpl 是 Service 的实现,通过组合各个子服务来实现
|
||||
@@ -34,6 +39,9 @@ type recipeServiceImpl struct {
|
||||
PigTypeService
|
||||
RecipeCoreService
|
||||
RecipeGenerateManager
|
||||
|
||||
recipeRepo repository.RecipeRepository
|
||||
ai ai.AI
|
||||
}
|
||||
|
||||
// NewRecipeService 创建一个新的 Service 实例
|
||||
@@ -46,6 +54,8 @@ func NewRecipeService(
|
||||
pigTypeService PigTypeService,
|
||||
recipeCoreService RecipeCoreService,
|
||||
recipeGenerateManager RecipeGenerateManager,
|
||||
recipeRepo repository.RecipeRepository,
|
||||
ai ai.AI,
|
||||
) Service {
|
||||
return &recipeServiceImpl{
|
||||
ctx: ctx,
|
||||
@@ -56,6 +66,8 @@ func NewRecipeService(
|
||||
PigTypeService: pigTypeService,
|
||||
RecipeCoreService: recipeCoreService,
|
||||
RecipeGenerateManager: recipeGenerateManager,
|
||||
recipeRepo: recipeRepo,
|
||||
ai: ai,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,3 +237,113 @@ func (r *recipeServiceImpl) GenerateRecipeWithPrioritizedStockRawMaterials(ctx c
|
||||
// 7. 返回创建的配方
|
||||
return recipe, nil
|
||||
}
|
||||
|
||||
// AIDiagnoseRecipe 使用 AI 为指定食谱生成诊断。
|
||||
func (s *recipeServiceImpl) AIDiagnoseRecipe(ctx context.Context, recipeID uint32, pigTypeID uint32) (string, models.AIModel, error) {
|
||||
serviceCtx, logger := logs.Trace(ctx, context.Background(), "AIDiagnoseRecipe")
|
||||
|
||||
// 1. 根据 recipeID 获取配方详情
|
||||
recipe, err := s.recipeRepo.GetRecipeByID(serviceCtx, recipeID)
|
||||
if err != nil {
|
||||
logger.Errorf("获取配方详情失败: %v", err)
|
||||
return "", s.ai.AIModel(), fmt.Errorf("获取配方详情失败: %w", err)
|
||||
}
|
||||
if recipe == nil {
|
||||
logger.Warnf("未找到配方,ID: %d", recipeID)
|
||||
return "", s.ai.AIModel(), fmt.Errorf("未找到配方,ID: %d", recipeID)
|
||||
}
|
||||
|
||||
// 2. 获取目标猪只类型信息
|
||||
pigType, err := s.GetPigTypeByID(serviceCtx, pigTypeID)
|
||||
if err != nil {
|
||||
logger.Errorf("获取猪只类型信息失败: %v", err)
|
||||
return "", s.ai.AIModel(), fmt.Errorf("获取猪只类型信息失败: %w", err)
|
||||
}
|
||||
if pigType == nil {
|
||||
logger.Warnf("未找到猪只类型,ID: %d", pigTypeID)
|
||||
return "", s.ai.AIModel(), fmt.Errorf("未找到猪只类型,ID: %d", pigTypeID)
|
||||
}
|
||||
|
||||
// 3. 定义 AI 输入结构体
|
||||
type ingredientNutrient struct {
|
||||
NutrientName string `json:"nutrient_name"`
|
||||
Value float32 `json:"value"`
|
||||
}
|
||||
|
||||
type recipeIngredient struct {
|
||||
RawMaterialName string `json:"raw_material_name"`
|
||||
Percentage float32 `json:"percentage"`
|
||||
Nutrients []ingredientNutrient `json:"nutrients"`
|
||||
}
|
||||
|
||||
type aiDiagnosisInput struct {
|
||||
RecipeName string `json:"recipe_name"`
|
||||
TargetPigType struct {
|
||||
Name string `json:"name"`
|
||||
} `json:"target_pig_type"`
|
||||
Ingredients []recipeIngredient `json:"ingredients"`
|
||||
}
|
||||
|
||||
// 4. 填充 AI 输入结构体
|
||||
input := aiDiagnosisInput{
|
||||
RecipeName: recipe.Name,
|
||||
}
|
||||
|
||||
input.TargetPigType.Name = fmt.Sprintf("%s-%s", pigType.Breed.Name, pigType.AgeStage.Name)
|
||||
|
||||
for _, ingredient := range recipe.RecipeIngredients {
|
||||
if ingredient.RawMaterial.ID == 0 {
|
||||
logger.Warnf("配方成分中存在未加载的原料信息,RecipeIngredientID: %d", ingredient.ID)
|
||||
continue
|
||||
}
|
||||
ing := recipeIngredient{
|
||||
RawMaterialName: ingredient.RawMaterial.Name,
|
||||
Percentage: ingredient.Percentage,
|
||||
}
|
||||
for _, rmn := range ingredient.RawMaterial.RawMaterialNutrients {
|
||||
if rmn.Nutrient.ID == 0 {
|
||||
logger.Warnf("原料营养成分中存在未加载的营养素信息,RawMaterialNutrientID: %d", rmn.ID)
|
||||
continue
|
||||
}
|
||||
ing.Nutrients = append(ing.Nutrients, ingredientNutrient{
|
||||
NutrientName: rmn.Nutrient.Name,
|
||||
Value: rmn.Value,
|
||||
})
|
||||
}
|
||||
input.Ingredients = append(input.Ingredients, ing)
|
||||
}
|
||||
|
||||
// 5. 序列化为 JSON 字符串
|
||||
jsonBytes, err := json.Marshal(input)
|
||||
if err != nil {
|
||||
logger.Errorf("序列化配方和猪只类型信息为 JSON 失败: %v", err)
|
||||
return "", s.ai.AIModel(), fmt.Errorf("序列化数据失败: %w", err)
|
||||
}
|
||||
jsonString := string(jsonBytes)
|
||||
|
||||
// 6. 构建 AI Prompt
|
||||
var promptBuilder strings.Builder
|
||||
promptBuilder.WriteString(`
|
||||
你是一个专业的动物营养师。请根据以下猪饲料配方数据,生成一份详细的、对养殖户友好的说明报告。
|
||||
说明报告应包括以下部分:
|
||||
1. 诊断猪只配方是否合理,如合理需要说明为什么合理, 如不合理需给出详细的改进建议。
|
||||
2. 关键成分分析:分析主要原料和营养成分的作用
|
||||
3. 使用建议:提供使用此配方的最佳实践和注意事项。
|
||||
\n`)
|
||||
promptBuilder.WriteString("```")
|
||||
promptBuilder.WriteString(jsonString)
|
||||
promptBuilder.WriteString("```")
|
||||
prompt := promptBuilder.String()
|
||||
|
||||
logger.Debugf("生成的 AI 诊断 Prompt: \n%s", prompt)
|
||||
|
||||
// 7. 调用 AI Manager 进行诊断
|
||||
diagnosisResult, err := s.ai.GenerateReview(serviceCtx, prompt)
|
||||
if err != nil {
|
||||
logger.Errorf("调用 AI Manager 诊断配方失败: %v", err)
|
||||
return "", s.ai.AIModel(), fmt.Errorf("AI 诊断失败: %w", err)
|
||||
}
|
||||
|
||||
logger.Infof("成功对配方 ID: %d (目标猪只类型 ID: %d) 进行 AI 诊断。", recipeID, pigTypeID)
|
||||
return diagnosisResult, s.ai.AIModel(), nil
|
||||
}
|
||||
|
||||
19
internal/infra/ai/ai.go
Normal file
19
internal/infra/ai/ai.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
)
|
||||
|
||||
// AI 定义了通用的 AI 管理接口。
|
||||
// 它可以用于处理各种 AI 相关的任务,例如文本生成、内容审核等。
|
||||
type AI interface {
|
||||
// GenerateReview 根据提供的文本内容生成评论。
|
||||
// prompt: 用于生成评论的输入文本。
|
||||
// 返回生成的评论字符串和可能发生的错误。
|
||||
GenerateReview(ctx context.Context, prompt string) (string, error)
|
||||
|
||||
// AIModel 返回当前使用的 AI 模型。
|
||||
AIModel() models.AIModel
|
||||
}
|
||||
73
internal/infra/ai/gemini.go
Normal file
73
internal/infra/ai/gemini.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/config"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
|
||||
"github.com/google/generative-ai-go/genai"
|
||||
"google.golang.org/api/option"
|
||||
)
|
||||
|
||||
// geminiImpl 是 Gemini AI 服务的实现。
|
||||
type geminiImpl struct {
|
||||
client *genai.GenerativeModel
|
||||
cfg *config.Gemini
|
||||
}
|
||||
|
||||
// NewGeminiAI 创建一个新的 geminiImpl 实例。
|
||||
func NewGeminiAI(ctx context.Context, cfg *config.AIConfig) (AI, error) {
|
||||
// 检查 API Key 是否存在
|
||||
if cfg.Gemini.APIKey == "" {
|
||||
return nil, fmt.Errorf("Gemini API Key 未配置")
|
||||
}
|
||||
|
||||
// 创建 Gemini 客户端
|
||||
genaiClient, err := genai.NewClient(ctx, option.WithAPIKey(cfg.Gemini.APIKey))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建 Gemini 客户端失败: %w", err)
|
||||
}
|
||||
|
||||
return &geminiImpl{
|
||||
client: genaiClient.GenerativeModel(cfg.Gemini.ModelName),
|
||||
cfg: &cfg.Gemini,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GenerateReview 根据提供的文本内容生成评论。
|
||||
func (g *geminiImpl) GenerateReview(ctx context.Context, prompt string) (string, error) {
|
||||
serviceCtx, logger := logs.Trace(ctx, context.Background(), "GenerateReview")
|
||||
logger.Debugf("开始调用 Gemini 生成评论,prompt: %s", prompt)
|
||||
|
||||
timeoutCtx, cancel := context.WithTimeout(serviceCtx, time.Duration(g.cfg.Timeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
resp, err := g.client.GenerateContent(timeoutCtx, genai.Text(prompt))
|
||||
if err != nil {
|
||||
logger.Errorf("调用 Gemini API 失败: %v", err)
|
||||
return "", fmt.Errorf("调用 Gemini API 失败: %w", err)
|
||||
}
|
||||
|
||||
if resp == nil || len(resp.Candidates) == 0 || len(resp.Candidates[0].Content.Parts) == 0 {
|
||||
logger.Warn("Gemini API 返回空内容或无候选评论")
|
||||
return "", fmt.Errorf("Gemini API 返回空内容或无候选评论")
|
||||
}
|
||||
|
||||
var review string
|
||||
for _, part := range resp.Candidates[0].Content.Parts {
|
||||
if txt, ok := part.(genai.Text); ok {
|
||||
review += string(txt)
|
||||
}
|
||||
}
|
||||
|
||||
logger.Debugf("成功从 Gemini 生成评论: %s", review)
|
||||
return review, nil
|
||||
}
|
||||
|
||||
func (g *geminiImpl) AIModel() models.AIModel {
|
||||
return models.AI_MODEL_GEMINI
|
||||
}
|
||||
@@ -236,11 +236,14 @@ type AlarmNotificationConfig struct {
|
||||
|
||||
// AIConfig AI 服务配置
|
||||
type AIConfig struct {
|
||||
Gemini struct {
|
||||
APIKey string `yaml:"api_key"` // Gemini API Key
|
||||
ModelName string `yaml:"model_name"` // Gemini 模型名称,例如 "gemini-pro"
|
||||
Timeout int `yaml:"timeout"` // AI 请求超时时间 (秒)
|
||||
} `yaml:"gemini"`
|
||||
Gemini Gemini `yaml:"gemini"`
|
||||
}
|
||||
|
||||
// Gemini 代表 Gemini AI 服务的配置
|
||||
type Gemini struct {
|
||||
APIKey string `yaml:"api_key"` // Gemini API Key
|
||||
ModelName string `yaml:"model_name"` // Gemini 模型名称,例如 "gemini-pro"
|
||||
Timeout int `yaml:"timeout"` // AI 请求超时时间 (秒)
|
||||
}
|
||||
|
||||
// NewConfig 创建并返回一个新的配置实例
|
||||
|
||||
@@ -66,8 +66,8 @@ func NewLogger(cfg config.LogConfig) *Logger {
|
||||
|
||||
// 5. 构建 Logger
|
||||
// zap.AddCaller() 会记录调用日志的代码行
|
||||
// zap.AddCallerSkip(1) 可以向上跳一层调用栈,如果我们将 logger.Info 等方法再封装一层,这个选项会很有用
|
||||
zapLogger := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1))
|
||||
// zap.AddCallerSkip(2) 可以向上跳两层调用栈,因为我们的日志方法被封装了两层 (Logger.Info -> Logger.logWithTrace)
|
||||
zapLogger := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(2))
|
||||
|
||||
return &Logger{sl: zapLogger.Sugar()}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,12 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type AIModel string
|
||||
|
||||
const (
|
||||
AI_MODEL_GEMINI AIModel = "Gemini"
|
||||
)
|
||||
|
||||
// Model 用于代替gorm.Model, 使用uint32以节约空间
|
||||
type Model struct {
|
||||
ID uint32 `gorm:"primarykey"`
|
||||
|
||||
Reference in New Issue
Block a user