diff --git a/cmd/server/wire_gen.go b/cmd/server/wire_gen.go index 542a4c3..4d9b139 100644 --- a/cmd/server/wire_gen.go +++ b/cmd/server/wire_gen.go @@ -34,7 +34,10 @@ func InitializeApp(configConfig *config.Config, allLogger log.AllLogger) (*serve publishService := service.NewPublishService(configConfig, publishBiz, authBiz, db) productImpl := impl.NewProductImpl(db) productService := service.NewProductService(configConfig, productImpl, authBiz) - productSourceService := service.NewProductSourceService(configConfig, productImpl, authBiz) + aiBiz := biz.NewAiBiz(platImpl) + productSourceImpl := impl.NewProductSourceImpl(db) + productBiz := biz.NewProductBiz(productImpl, productSourceImpl) + productSourceService := service.NewProductSourceService(configConfig, productImpl, authBiz, aiBiz, productBiz) appModule := router.NewAppModule(configConfig, appService, loginService, publishService, productService, productSourceService) routerServer := router.NewRouterServer(appModule) app := server.NewHTTPServer(routerServer) diff --git a/go.mod b/go.mod index 541d2d8..3b9a971 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.25.4 require ( gitea.cdlsxd.cn/self-tools/l_request v1.0.8 + github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible github.com/go-kratos/kratos/v2 v2.9.2 github.com/go-playground/validator/v10 v10.30.2 github.com/go-rod/rod v0.116.2 @@ -50,6 +51,7 @@ require ( golang.org/x/crypto v0.49.0 // indirect golang.org/x/sys v0.42.0 // indirect golang.org/x/text v0.35.0 // indirect + golang.org/x/time v0.15.0 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v2 v2.2.8 // indirect ) diff --git a/go.sum b/go.sum index e16ee16..1600f3b 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ gitea.cdlsxd.cn/self-tools/l_request v1.0.8/go.mod h1:Qf4hVXm2Eu5vOvwXk8D7U0q/ae gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g= +github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= @@ -166,6 +168,8 @@ golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= diff --git a/internal/ai/hsyq.go b/internal/ai_tool/hsyq.go similarity index 89% rename from internal/ai/hsyq.go rename to internal/ai_tool/hsyq.go index ed2a177..a2e9a3a 100644 --- a/internal/ai/hsyq.go +++ b/internal/ai_tool/hsyq.go @@ -1,4 +1,4 @@ -package third_party +package ai_tool import ( "context" @@ -147,20 +147,6 @@ func (h *Hsyq) RequestHsyqJson(ctx context.Context, key string, modelName string return resp, err } -// []*model.ChatCompletionMessage{ -// { -// Role: model.ChatMessageRoleSystem, -// Content: &model.ChatCompletionMessageContent{ -// StringValue: volcengine.String("你是豆包,是由字节跳动开发的 AI 人工智能助手"), -// }, -// }, -// { -// Role: model.ChatMessageRoleUser, -// Content: &model.ChatCompletionMessageContent{ -// StringValue: volcengine.String("常见的十字花科植物有哪些?"), -// }, -// }, -// }, func (h *Hsyq) RequestHsyqBot(ctx context.Context, key string, botId string, message []*model.ChatCompletionMessage) ([]byte, error) { req := model.BotChatCompletionRequest{ BotId: botId, diff --git a/internal/biz/ai.go b/internal/biz/ai.go index 7813fc8..17668e0 100644 --- a/internal/biz/ai.go +++ b/internal/biz/ai.go @@ -1,4 +1,62 @@ package biz -type Ai struct { +import ( + "context" + "geo/internal/data/impl" + "geo/internal/data/model" + "geo/internal/entitys" + "geo/pkg" + + volmodle "github.com/volcengine/volcengine-go-sdk/service/arkruntime/model" + "github.com/volcengine/volcengine-go-sdk/volcengine" + "xorm.io/builder" +) + +type AiBiz struct { + platImpl *impl.PlatImpl +} + +func NewAiBiz(platImpl *impl.PlatImpl) *AiBiz { + return &AiBiz{ + platImpl: platImpl, + } +} + +func (a *AiBiz) CreateArticlePrompt(ctx context.Context, data *entitys.BotChat) []*volmodle.ChatCompletionMessage { + mes := []*volmodle.ChatCompletionMessage{ + { + Role: volmodle.ChatMessageRoleUser, + Content: &volmodle.ChatCompletionMessageContent{ + StringValue: volcengine.String(pkg.JsonStringIgonErr(data)), + }, + }, + } + var plats []*model.Plat + cond := builder.NewCond(). + And(builder.Eq{"plat_type": 1}). + And(builder.Eq{"status": 1}) + _, err := a.platImpl.GetListToStruct(ctx, &cond, nil, &plats, "id asc") + if err != nil { + return mes + } + var platList = &entitys.PlatList{ + Desc: "平台列表", + PlatItem: make([]*entitys.PlatItem, 0, len(plats)), + } + for _, plat := range plats { + platList.PlatItem = append(platList.PlatItem, &entitys.PlatItem{ + Platform: plat.Name, + PlatformIndex: plat.Index, + }) + } + if len(platList.PlatItem) > 0 { + mes = append(mes, &volmodle.ChatCompletionMessage{ + Role: volmodle.ChatMessageRoleAssistant, + Content: &volmodle.ChatCompletionMessageContent{ + StringValue: volcengine.String(pkg.JsonStringIgonErr(platList)), + }, + }) + } + + return mes } diff --git a/internal/biz/product.go b/internal/biz/product.go new file mode 100644 index 0000000..f1169e4 --- /dev/null +++ b/internal/biz/product.go @@ -0,0 +1,46 @@ +package biz + +import ( + "context" + "geo/internal/data/impl" + "geo/internal/data/model" + "geo/internal/entitys" + + "github.com/go-viper/mapstructure/v2" +) + +type ProductBiz struct { + productImpl *impl.ProductImpl + productSourceImpl *impl.ProductSourceImpl +} + +func NewProductBiz(productImpl *impl.ProductImpl, productSourceImpl *impl.ProductSourceImpl) *ProductBiz { + return &ProductBiz{ + productImpl: productImpl, + productSourceImpl: productSourceImpl, + } +} + +func (p *ProductBiz) GetBrandInfo(ctx context.Context, productId int32) (*entitys.BrandInfo, error) { + var product model.Product + err := p.productImpl.GetByKey(ctx, p.productImpl.PrimaryKey(), productId, &product) + if err != nil { + return nil, err + } + var BrandInfo entitys.BrandInfo + err = mapstructure.Decode(product, &BrandInfo) + + return &BrandInfo, err +} + +func (p *ProductBiz) CreateAndUploadArticle(ctx context.Context, content string) error { + //var product model.Product + //err := p.productImpl.GetByKey(ctx, p.productImpl.PrimaryKey(), productId, &product) + //if err != nil { + // return nil, err + //} + //var BrandInfo entitys.BrandInfo + //err = mapstructure.Decode(product, &BrandInfo) + + return nil +} diff --git a/internal/biz/provider_set.go b/internal/biz/provider_set.go index f214121..168fd63 100644 --- a/internal/biz/provider_set.go +++ b/internal/biz/provider_set.go @@ -7,4 +7,6 @@ import ( var ProviderSetBiz = wire.NewSet( NewPublishBiz, NewAuthBiz, + NewAiBiz, + NewProductBiz, ) diff --git a/internal/config/config.go b/internal/config/config.go index 43145c3..73a839b 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -10,6 +10,16 @@ type Config struct { Server ServerConfig `mapstructure:"server"` DB DB `mapstructure:"db"` Sys Sys `mapstructure:"sys"` + Hsyq Hsyq `mapstructure:"hsyq"` + Oss Oss `mapstructure:"oss"` +} + +type Oss struct { + AccessKey string `mapstructure:"access_key"` + SecretKey string `mapstructure:"secret_key"` + Bucket string `mapstructure:"bucket"` + Domain string `mapstructure:"domain"` + Endpoint string `mapstructure:"endpoint"` } // ServerConfig 服务器配置 @@ -18,6 +28,10 @@ type ServerConfig struct { Host string `mapstructure:"host"` } +type Hsyq struct { + ApiKey string `mapstructure:"api_key"` +} + type DB struct { Driver string `mapstructure:"driver"` Source string `mapstructure:"source"` @@ -70,5 +84,15 @@ func LoadConfig() (*Config, error) { ChromePath: filepath.Join(BaseDir, "chrome", "chrome.exe"), ChromeDataDir: filepath.Join(BaseDir, "chrome_data"), }, + Hsyq: Hsyq{ + ApiKey: "236ba4b6-9daa-4755-b22f-2fd274cd223a", + }, + Oss: Oss{ + AccessKey: "LTAI5tGGZzjf3tvqWk8SQj2G", + SecretKey: "S0NKOAUaYWoK4EGSxrMFmYDzllhvpq", + Bucket: "attachment-public", + Domain: "https://attachment-public.oss-cn-hangzhou.aliyuncs.com", + Endpoint: "https://oss-cn-hangzhou.aliyuncs.com", + }, }, nil } diff --git a/internal/data/model/plat.gen.go b/internal/data/model/plat.gen.go index e129363..2b1314b 100644 --- a/internal/data/model/plat.gen.go +++ b/internal/data/model/plat.gen.go @@ -11,6 +11,7 @@ type Plat struct { ID int32 `gorm:"column:id;primaryKey" json:"id"` Name string `gorm:"column:name;not null" json:"name"` Index string `gorm:"column:index;not null" json:"index"` + PlatType int32 `gorm:"column:plat_type;not null;default:1" json:"plat_type"` ImgURL string `gorm:"column:img_url;not null" json:"img_url"` LoginURL string `gorm:"column:login_url;not null" json:"login_url"` EditURL string `gorm:"column:edit_url;not null" json:"edit_url"` diff --git a/internal/entitys/publish.go b/internal/entitys/publish.go index 30489d5..431fada 100644 --- a/internal/entitys/publish.go +++ b/internal/entitys/publish.go @@ -40,3 +40,47 @@ const ( NotifyTypePublish NotifyType = 1 NotifyTypeToken NotifyType = 1 ) + +type BotChat struct { + Question string `json:"question"` + ArticleType string `json:"article_type"` + BrandInfo *BrandInfo `json:"brand_info"` +} + +type BrandInfo struct { + Name string `json:"name"` + Industry string `json:"industry"` + Type string `json:"type"` + ProductOrService string `json:"product_or_service"` + Advantages string `json:"advantages"` + Story string `json:"story"` + Problem string `json:"problem"` + Background string `json:"background"` + Case string `json:"case"` + Other string `json:"other"` + ServiceCope string `json:"service_cope"` + TargetAudience string `json:"target_audience"` +} + +type PlatList struct { + Desc string `json:"question"` + PlatItem []*PlatItem `json:"plat_item"` +} + +type PlatItem struct { + Platform string `json:"platform"` + PlatformIndex string `json:"platform_index"` +} + +type BotChatResponse struct { + Title string `json:"title"` + Content string `json:"content"` + WordCount int `json:"wordCount"` + Tag []string `json:"tag"` + RecommendPlatform []struct { + Platform string `json:"platform"` + PlatformIndex string `json:"platform_index"` + Score string `json:"score"` + Reason string `json:"reason"` + } `json:"recommend_platform"` +} diff --git a/internal/entitys/request.go b/internal/entitys/request.go index ac3696b..ff5fc70 100644 --- a/internal/entitys/request.go +++ b/internal/entitys/request.go @@ -135,8 +135,9 @@ type ( ProductSourceCreateRequest struct { AccessToken string `json:"access_token" validate:"required" zh:"access_token"` - UserIndex string `json:"user_index" validate:"required" zh:"用户索引"` - PlatIndex string `json:"plat_index" validate:"required" zh:"平台索引"` + Id int32 `json:"id" validate:"required" zh:"产品id"` + Ques string `json:"ques" validate:"required" zh:"问题"` + ArticleType string `json:"article_type" validate:"required" zh:"文章类型"` } ProductSourceListRequest struct { diff --git a/internal/service/product_source.go b/internal/service/product_source.go index 5d8f45f..88b22cb 100644 --- a/internal/service/product_source.go +++ b/internal/service/product_source.go @@ -1,11 +1,14 @@ package service import ( + "encoding/json" + "geo/internal/ai_tool" "geo/internal/biz" "geo/internal/config" "geo/internal/data/impl" + "geo/internal/data/model" "geo/internal/entitys" - + "github.com/go-viper/mapstructure/v2" "github.com/gofiber/fiber/v2" ) @@ -13,22 +16,44 @@ type ProductSourceService struct { cfg *config.Config productImpl *impl.ProductImpl authBiz *biz.AuthBiz + aiBiz *biz.AiBiz + productBiz *biz.ProductBiz } -func NewProductSourceService(cfg *config.Config, ProductImpl *impl.ProductImpl, authBiz *biz.AuthBiz) *ProductSourceService { +func NewProductSourceService(cfg *config.Config, ProductImpl *impl.ProductImpl, authBiz *biz.AuthBiz, aiBiz *biz.AiBiz, productBiz *biz.ProductBiz) *ProductSourceService { return &ProductSourceService{ cfg: cfg, productImpl: ProductImpl, authBiz: authBiz, + aiBiz: aiBiz, + productBiz: productBiz, } } func (p *ProductSourceService) Create(c *fiber.Ctx, req *entitys.ProductSourceCreateRequest) error { // 验证token - _, _, err := p.authBiz.UserAndTokenValid(c.UserContext(), req.UserIndex, req.AccessToken) + _, err := p.authBiz.ValidateAccessToken(c.UserContext(), req.AccessToken) if err != nil { return err } + brandInfo, err := p.productBiz.GetBrandInfo(c.UserContext(), req.Id) + if err != nil { + return err + } + BotChatMes := &entitys.BotChat{ + Question: req.Ques, + ArticleType: req.ArticleType, + BrandInfo: brandInfo, + } + mes := p.aiBiz.CreateArticlePrompt(c.UserContext(), BotChatMes) + content, err := ai_tool.NewHsyq().RequestHsyqBot(c.UserContext(), p.cfg.Hsyq.ApiKey, "bot-20260413000114-8bw62", mes) + if err != nil { + return err + } + var resp entitys.BotChatResponse + if err := json.Unmarshal(content, &resp); err != nil { + return err + } return nil } diff --git a/plugins/ys.md b/plugins/ys.md new file mode 100644 index 0000000..7f31a75 --- /dev/null +++ b/plugins/ys.md @@ -0,0 +1,43 @@ +# 成都云算科技有限公司 - 企业信息概览 + +## name(品牌/产品/公司名称) +成都云算科技有限公司 + +## industry(所属行业) +软件与信息技术服务业/房地产数字化 + +## type(产品类型) +数字化精准营销整体解决方案提供商 + +## productOrService(主营业务) +房地产数字化营销SaaS平台、CRM售楼软件、云置业、云获客、云渠道、云风控、云售楼、云收银、云开盘、云交房、云商业等15大云系列产品,智慧案场解决方案,渠道风控系统,数字楼盘展示,VR看房,线上开盘系统 + +## keyword(关键词) +房地产数字化营销解决方案,房地产SaaS平台,售楼软件,渠道风控,智慧案场,房企数字化转型 + +## advantages(核心优势) +十七年房地产数字化深耕经验,行业排名首位;国家高新技术企业认证;拥有8项专利、60多项软件著作权;全国25城布局服务网点,累计服务3000+企业、500+项目;三大体系十五大云产品覆盖营销全场景;提供标准API接口打通阿里云、旷视科技、用友、金蝶等生态;支持PC/iOS/安卓/小程序/H5多终端;7×24小时运维保障;客户含万达集团、中铁二局、中信国安、传化集团等头部企业 + +## serviceScope(服务范围) +全国范围,总部成都高新区,在重庆、贵阳、昆明、武汉、深圳、南宁、西安等25个核心城市设立直属服务网点 + +## targetAudience(目标群体) +全国性大型房企、区域龙头开发商、本土中小开发企业、房产代理公司、商业地产运营方;有土地储备待开发的开发商、即将开盘的项目方、对现有数字化系统成本高或效果不满意寻求更换的房企 + +## story(发展故事) +成都云算科技前身为2008年成立的云算房产软件事业部,初期推出CRM售楼软件切入市场。2013年正式成立公司,2015年获国家高新企业认证,2016年移动端上线,2017年启动SaaS平台建设,2019年云系列产品全面上线,2020-2023年快速扩展至全国25城,2024年实施全国服务计划。十七年从单一软件商成长为覆盖线上拓客、渠道管理、案场运营、交易收款、风险管控、售后交房、商业运营全链条的数字化营销领军企业 + +## problem(解决痛点) +房企营销普遍面临销售动作失控(管理者看不到过程)、过度依赖个人经验(能力无法复制)、主观数据失真(汇报数据加工过)、沟通成本过高(大量时间用于开会汇报而非销售)等问题,导致营销费效比低、舞弊风险高、决策滞后。典型的低效组织闭环:越失控越依赖人,越依赖人越失真,越失真越沟通,越沟通越失控 + +## background(信任背书) +国家高新技术企业认证;拥有二级教授、高校博导、行业资深专家组成的顾问团队;项目获多位国家及省级领导视察指导;合作生态含中国电信/移动/联通、阿里云、旷视科技、中国银联、通联支付、法大大、企业微信、钉钉、用友、金蝶云等头部企业;服务客户含万达集团、中铁二局、中信国安、传化集团、中慧实业集团、四川三叶集团、广西华宏地产、南宁威宁集团等 + +## case(品牌案例) +某全国性房企成都区域项目,使用云算科技数字化营销方案前,渠道舞弊频发、客户归属争议不断、案场数据手工统计滞后。部署云渠道+云风控+云销售系统后,实现渠道报备带看全流程线上化,刷脸核验杜绝虚假带看,客户判客准确率提升至99%,案场数据自动生成日报周报,营销费效比降低25%,项目去化周期缩短30% + +## other(其他信息) +公司愿景为‘成为最优质的数字智能服务商’,秉持‘专注、专业、诚信、务实、高效、共赢’理念。提供1+1+1售楼管理体系(线上+外场+内场),智能报表系统覆盖5大类19+项核心报表自动推送,支持刷脸核验、无感抓拍、人证核验等AI能力。总部设于成都市高新区,全国25个核心城市设立直属服务网点,实现就近快速响应 + +## target_audience(目标客户群体) +全国性大型房企、区域龙头开发商、本土中小开发企业、房产代理公司、商业地产运营方;有土地储备待开发的开发商、即将开盘的项目方、对现有数字化系统成本高或效果不满意寻求更换的房企 \ No newline at end of file diff --git a/utils/provider_set.go b/utils/provider_set.go index 09fee00..95719a9 100644 --- a/utils/provider_set.go +++ b/utils/provider_set.go @@ -1,9 +1,12 @@ package utils import ( + "geo/utils/utils_oss" + "github.com/google/wire" ) var ProviderUtils = wire.NewSet( NewGormDb, + utils_oss.NewClient, ) diff --git a/utils/utils_oss/client.go b/utils/utils_oss/client.go new file mode 100644 index 0000000..80d6f8f --- /dev/null +++ b/utils/utils_oss/client.go @@ -0,0 +1,57 @@ +package utils_oss + +import ( + "bytes" + "fmt" + "geo/internal/config" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" + "github.com/go-kratos/kratos/v2/log" +) + +type Client struct { + config config.Oss + client *oss.Client + bucket *oss.Bucket +} + +// NewClient 初始化 OSS 客户端 +func NewClient(cfg *config.Config) (*Client, error) { + client, err := oss.New(cfg.Oss.Endpoint, cfg.Oss.AccessKey, cfg.Oss.SecretKey) + if err != nil { + return nil, fmt.Errorf("oss new client failed: %v", err) + } + + bucket, err := client.Bucket(cfg.Oss.Bucket) + if err != nil { + return nil, fmt.Errorf("oss get bucket failed: %v", err) + } + + return &Client{ + config: cfg.Oss, + client: client, + bucket: bucket, + }, nil +} + +// UploadBytes 上传字节数组到 OSS +// objectKey: OSS 中的文件路径,例如 "ai_scheduler/test.png" +// fileBytes: 文件内容 +// 返回: 文件的访问 URL +func (c *Client) UploadBytes(objectKey string, fileBytes []byte) (string, error) { + err := c.bucket.PutObject(objectKey, bytes.NewReader(fileBytes)) + if err != nil { + log.Errorf("oss PutObject failed: %v", err) + return "", err + } + + // 构造返回 URL + var url string + if c.config.Domain != "" { + url = fmt.Sprintf("%s/%s", c.config.Domain, objectKey) + } else { + // 这里简单处理协议头 + url = fmt.Sprintf("https://%s.%s/%s", c.config.Bucket, c.config.Endpoint, objectKey) + } + return url, nil +}