Ver Fonte

修改列表

renjiaojiao há 3 semanas atrás
pai
commit
2a45996ba8

+ 1 - 1
api/etc/message-api.yaml

@@ -1,7 +1,7 @@
 Name: message-api
 Host: 0.0.0.0
 Port: 8911
-
+Timeout: 100000
 Gateway:
   ServerCode: message
   Etcd:

+ 8 - 0
entity/message.go

@@ -4,6 +4,7 @@ import (
 	"app.yhyue.com/moapp/jybase/mail"
 	m "app.yhyue.com/moapp/jybase/mongodb"
 	"app.yhyue.com/moapp/jybase/mysql"
+	"bp.jydev.jianyu360.cn/BaseService/fileCenter/rpc/filecenter"
 	"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
 	"time"
 
@@ -14,6 +15,8 @@ import (
 var Engine *xorm.Engine
 var Mysql *mysql.Mysql
 var BaseMysql *mysql.Mysql
+var BiService *mysql.Mysql
+var JyBi *mysql.Mysql
 var MessageColumn []map[string]interface{}
 var MQFW m.MongodbSim
 var Bidding m.MongodbSim
@@ -24,6 +27,11 @@ var SaveConcurrencyChan chan int //  定义保存消息并发
 var ClassSearchMap map[int64][]MsgClass
 var ClassMap map[int64]MsgClass
 var ClickhouseConn driver.Conn
+var (
+	OssBucketName string
+	OssUrl        string
+	FileCenterRpc filecenter.FileCenter
+)
 
 type MsgClass struct {
 	MsgType int64  `json:"msgType"`

+ 22 - 1
rpc/etc/message.yaml

@@ -5,7 +5,7 @@ Etcd:
   - 127.0.0.1:2379
   - 127.0.0.1:2379
   Key: message.rpc
-Timeout: 10000
+Timeout: 300000
 PushGrpcServer: 192.168.3.206:5566
 #Mysql: root:=PDT49#80Z!RVv52_z@tcp(192.168.3.217:4000)/messageCenter?charset=utf8mb4&parseTime=true&loc=Local
 Mongodb:
@@ -40,6 +40,20 @@ BaseSource:
   PassWord: =PDT49#80Z!RVv52_z
   MaxOpenConns: 50
   MaxIdleConns: 50
+BiService:
+  DbName: bi_service
+  Address: 172.20.45.129:4000
+  UserName: root
+  PassWord: =PDT49#80Z!RVv52_z
+  MaxOpenConns: 50
+  MaxIdleConns: 50
+JySubject:
+  DbName: jianyu_subjectdb_test
+  Address: 172.20.45.129:4000
+  UserName: root
+  PassWord: =PDT49#80Z!RVv52_z
+  MaxOpenConns: 50
+  MaxIdleConns: 50
 RedisAddr: "msgCount=172.20.45.129:1712"
 FileSystemConf:
   Etcd:
@@ -96,5 +110,12 @@ RedisFailureTime: 120
 ThreadCount: 15
 MsgLogLimit: 3000
 MsgTitlePS: "【商机情报】%s有新的采购计划"
+FileCenterRpc:
+  Etcd:
+    Hosts:
+      - 172.31.31.205:2379
+    Key: filecenter.rpc
+OssBucketName: jytest2022
+OssUrl: https://jytest2022.oss-cn-beijing.aliyuncs.com
 
 

+ 77 - 1
rpc/internal/common/MaterialService.go

@@ -2,14 +2,21 @@ package common
 
 import (
 	"app.yhyue.com/moapp/MessageCenter/entity"
+	"app.yhyue.com/moapp/jybase/date"
+	"crypto/rand"
+	"fmt"
 	"github.com/gogf/gf/v2/util/gconv"
+	"log"
+	"math/big"
+	"net/url"
 	"strings"
+	"time"
 )
 
 func MaterialDetail(msgId, positionId int64) (dataMap map[string]interface{}) {
 	//查询到内容
 	dataMap = map[string]interface{}{}
-	data := entity.Mysql.SelectBySql("SELECT a.id,a.title,a.content,a.createtime,a.id,a.group_id,a.link,a.msg_type,b.file_url,b.material_content,b.qrcode_url,b.task_description,b.task_name,b.img_webpage FROM jianyu.message_send_log a LEFT JOIN bi_service.operating_materials b ON a.id = b.msg_id WHERE a.id = ?", msgId)
+	data := entity.Mysql.SelectBySql("SELECT a.id,a.title,a.content,a.createtime,a.group_id,a.link,a.msg_type,b.file_url,b.material_content,b.qrcode_url,b.task_description,b.task_name,b.img_webpage,b.receive_user_name,b.receive_position_id,b.id as mid FROM jianyu.message_send_log a LEFT JOIN bi_service.operating_materials b ON a.id = b.msg_id WHERE a.id = ?", msgId)
 	if data != nil && len(*data) > 0 {
 		dataMap = (*data)[0]
 		//查询图片信息
@@ -28,3 +35,72 @@ func MaterialDetail(msgId, positionId int64) (dataMap map[string]interface{}) {
 	return nil
 
 }
+
+func PersonImageSave(imgUrl string, msgId, positionId, materialsId int64) bool {
+	saveMap := map[string]interface{}{
+		"file_url":     imgUrl,
+		"msg_id":       msgId,
+		"position_id":  positionId,
+		"materials_id": materialsId,
+		"createtime":   time.Now().Format(date.Date_Full_Layout),
+	}
+	return entity.BiService.Insert("operating_materials_attachment", saveMap) > 0
+}
+
+func PersonImgSaveComposite(fileUrl, qrcodeUrl, name string, positionId int64) ([]byte, error) {
+	//根据职位id获取到对应的渠道码
+	code := ""
+	data := entity.JyBi.FindOne("dwd_d_userbase_belongto_rulecode", map[string]interface{}{"position_id": positionId}, "code", "")
+	if data != nil && len(*data) > 0 {
+		code = gconv.String((*data)["code"])
+	} else {
+		//没有code,生成一个
+		code = generateRandomString(6)
+		entity.JyBi.Insert("dwd_d_userbase_belongto_rulecode", map[string]interface{}{
+			"code":        code,
+			"pcode":       "04",
+			"level":       "2",
+			"name":        "",
+			"remark":      "人员渠道码",
+			"SZ_LEVEL":    1,
+			"SZ_LEAF":     1,
+			"SZ_PID0":     "04",
+			"SZ_PID1":     code,
+			"position_id": positionId,
+		})
+	}
+	//合成图片
+	ok, err := hasAnyParameters(qrcodeUrl)
+	if err != nil {
+		return nil, err
+	}
+	if ok {
+		qrcodeUrl = fmt.Sprintf("%s&personnelChannel=%s", qrcodeUrl, code)
+	} else {
+		qrcodeUrl = fmt.Sprintf("%s?personnelChannel=%s", qrcodeUrl, code)
+	}
+	log.Println("*********二维码图片链接:", qrcodeUrl)
+	err, imgByte := compositeImage(fileUrl, qrcodeUrl)
+	return imgByte, err
+}
+
+func generateRandomString(length int) string {
+	const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
+	b := make([]byte, length)
+	for i := range b {
+		// 安全地生成随机索引 (0 到 len(charset)-1)
+		idx, _ := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
+		b[i] = charset[idx.Int64()]
+	}
+	return string(b)
+}
+
+func hasAnyParameters(rawURL string) (bool, error) {
+	parsedURL, err := url.Parse(rawURL)
+	if err != nil {
+		return false, err
+	}
+
+	// 检查查询参数或片段标识符中的参数
+	return parsedURL.RawQuery != "" || (parsedURL.Fragment != "" && strings.Contains(parsedURL.Fragment, "=")), nil
+}

+ 200 - 0
rpc/internal/common/compositeImages.go

@@ -0,0 +1,200 @@
+package common
+
+import (
+	"bytes"
+	"fmt"
+	"github.com/disintegration/imaging"
+	"github.com/fogleman/gg"
+	"github.com/gogf/gf/v2/util/gconv"
+	"github.com/nfnt/resize"
+	"github.com/skip2/go-qrcode"
+	"image"
+	"image/color"
+	"image/draw"
+	"image/jpeg"
+	"image/png"
+	"math"
+	"net/http"
+)
+
+func compositeImage(backgroundURL, qrContent string) (err error, imgByte []byte) {
+	// 配置参数
+	//backgroundURL := "https://images.unsplash.com/photo-1501854140801-50d01698950b" // 示例背景图片
+	//qrContent := "https://github.com/zeromicro/go-zero"                           // 二维码内容
+	//outputPath := "output.jpg" // 输出文件路径
+	//margin := 20         // 二维码距边缘的边距
+	rightMargin := 60
+	bottomMargin := 100
+	qrSize := 160       // 二维码原始大小(像素)
+	cornerRadius := 5.0 // 二维码圆角半径
+	borderCornerRadius := 10.0
+	borderSize := 10     // 二维码白色边框大小
+	shadowBlur := 0.0    // 阴影模糊度
+	shadowOpacity := 0.0 // 阴影透明度
+
+	// 创建带效果的二维码并合成到背景右下角
+	imgByte, err = createQRComposite(backgroundURL, qrContent, rightMargin, bottomMargin, qrSize, cornerRadius, borderCornerRadius, gconv.Float64(borderSize), shadowBlur, shadowOpacity)
+	if err != nil {
+		return err, nil
+	}
+	return nil, imgByte
+}
+
+// 创建二维码合成图片
+func createQRComposite(backgroundURL, qrContent string, rightMargin, bottomMargin, qrSize int, cornerRadius, borderCornerRadius, borderSize, shadowBlur, shadowOpacity float64) ([]byte, error) {
+	// 1. 下载背景图片
+	bgImg, imgType, err := downloadImage(backgroundURL)
+	if err != nil {
+		return nil, err
+	}
+
+	// 2. 生成带效果的二维码
+	qrImg, err := generateStyledQRCode(qrContent, qrSize, color.Black, color.White, cornerRadius, borderCornerRadius, borderSize, shadowBlur, shadowOpacity)
+	if err != nil {
+		return nil, err
+	}
+
+	// 3. 计算二维码在右下角的位置
+	bgBounds := bgImg.Bounds()
+	qrBounds := qrImg.Bounds()
+	pos := image.Point{
+		X: bgBounds.Dx() - qrBounds.Dx() - rightMargin,
+		Y: bgBounds.Dy() - qrBounds.Dy() - bottomMargin,
+	}
+
+	// 4. 调整二维码大小(如果需要)
+	finalQR := qrImg
+	if bgBounds.Dx() < 800 {
+		// 背景图较小,缩小二维码
+		targetWidth := bgBounds.Dx() / 4
+		finalQR = resize.Resize(uint(targetWidth), 0, qrImg, resize.Lanczos3)
+		// 重新计算位置
+		pos = image.Point{
+			X: bgBounds.Dx() - finalQR.Bounds().Dx() - rightMargin,
+			Y: bgBounds.Dy() - finalQR.Bounds().Dy() - bottomMargin,
+		}
+	}
+
+	// 5. 合成图片
+	resultImg := image.NewRGBA(bgBounds)
+	draw.Draw(resultImg, bgBounds, bgImg, image.Point{}, draw.Src)
+	draw.Draw(resultImg, finalQR.Bounds().Add(pos), finalQR, image.Point{}, draw.Over)
+
+	// 6. 保存结果
+	//return saveImage(outputPath, resultImg, 95)
+	imgByte, err := encodeImageToBytes(resultImg, imgType, 90)
+	if err != nil {
+		return nil, err
+	}
+	return imgByte, nil
+}
+
+// 下载图片
+func downloadImage(url string) (image.Image, string, error) {
+	resp, err := http.Get(url)
+	if err != nil {
+		return nil, "", err
+	}
+	defer resp.Body.Close()
+
+	img, imgType, err := image.Decode(resp.Body)
+	if err != nil {
+		return nil, "", err
+	}
+
+	return img, imgType, nil
+}
+
+func generateStyledQRCode(content string, size int, fgColor, bgColor color.Color, cornerRadius, borderCornerRadius, borderSize, shadowBlur, shadowOpacity float64) (image.Image, error) {
+	// 生成基本二维码
+	qr, err := qrcode.New(content, qrcode.Highest)
+	if err != nil {
+		return nil, err
+	}
+	qr.ForegroundColor = fgColor
+	qr.BackgroundColor = bgColor
+	qr.DisableBorder = true
+
+	// 创建基本二维码图片
+	baseImg := qr.Image(size)
+
+	// 计算带边框的总大小
+	borderWidth := int(math.Ceil(borderSize))
+	totalSize := size + 2*borderWidth
+
+	// 创建带透明背景的总画布
+	dc := gg.NewContext(totalSize, totalSize)
+	dc.SetColor(color.Transparent)
+	dc.Clear()
+
+	// 1. 绘制圆角白色边框
+	dc.SetColor(color.White)
+	dc.DrawRoundedRectangle(0, 0, float64(totalSize), float64(totalSize), borderCornerRadius+borderSize)
+	dc.Fill()
+
+	// 2. 创建二维码的圆角蒙版
+	qrDC := gg.NewContext(size, size)
+	qrDC.SetColor(color.Transparent)
+	qrDC.Clear()
+
+	if cornerRadius > 0 {
+		qrDC.DrawRoundedRectangle(0, 0, float64(size), float64(size), cornerRadius)
+		qrDC.Clip()
+	}
+
+	// 3. 绘制二维码到蒙版
+	qrDC.DrawImage(baseImg, 0, 0)
+
+	// 4. 将带蒙版的二维码绘制到总画布(居中)
+	dc.DrawImage(qrDC.Image(), borderWidth, borderWidth)
+
+	// 添加阴影效果
+	if shadowBlur > 0 && shadowOpacity > 0 {
+		shadowImg := addShadow(dc.Image(), shadowBlur, shadowOpacity)
+		return shadowImg, nil
+	}
+
+	return dc.Image(), nil
+}
+
+// 添加阴影效果
+func addShadow(img image.Image, blur, opacity float64) image.Image {
+	bounds := img.Bounds()
+	dc := gg.NewContext(bounds.Dx()+int(blur*2), bounds.Dy()+int(blur*2))
+
+	// 绘制阴影
+	dc.SetRGBA(0, 0, 0, opacity)
+	dc.DrawRectangle(blur, blur, float64(bounds.Dx()), float64(bounds.Dy()))
+	dc.Fill()
+
+	// 应用高斯模糊
+	shadow := dc.Image()
+	blurred := imaging.Blur(shadow, blur)
+
+	// 创建新画布绘制二维码和阴影
+	dc = gg.NewContext(bounds.Dx()+int(blur*2), bounds.Dy()+int(blur*2))
+	dc.DrawImage(blurred, 0, 0)
+	dc.DrawImage(img, int(blur), int(blur))
+
+	return dc.Image()
+}
+
+// 编码图片为字节流
+func encodeImageToBytes(img image.Image, format string, quality int) ([]byte, error) {
+	buf := new(bytes.Buffer)
+
+	switch format {
+	case "png":
+		if err := png.Encode(buf, img); err != nil {
+			return nil, err
+		}
+	case "jpg", "jpeg":
+		if err := jpeg.Encode(buf, img, &jpeg.Options{Quality: quality}); err != nil {
+			return nil, err
+		}
+	default:
+		return nil, fmt.Errorf("不支持的图片格式: %s", format)
+	}
+
+	return buf.Bytes(), nil
+}

+ 8 - 3
rpc/internal/config/config.go

@@ -6,9 +6,14 @@ import (
 
 type Config struct {
 	zrpc.RpcServerConf
-	//FileCenterRpc zrpc.RpcClientConf
-	DataSource *mysqlConfig // 手动代码
-	BaseSource *mysqlConfig // 手动代码
+	FileCenterRpc zrpc.RpcClientConf
+	OssBucketName string       `json:"OssBucketName"`
+	OssUrl        string       `json:"OssUrl"`
+	DataSource    *mysqlConfig // 手动代码
+	BaseSource    *mysqlConfig // 手动代码
+	BiService     *mysqlConfig // 手动代码
+	JySubject     *mysqlConfig // 手动代码
+
 	WebRpcPort int
 	//Mysql           string
 	RedisAddr       string `json:"RedisAddr"`

+ 45 - 0
rpc/internal/logic/mldetailslogic.go

@@ -1,13 +1,18 @@
 package logic
 
 import (
+	"app.yhyue.com/moapp/MessageCenter/entity"
 	service "app.yhyue.com/moapp/MessageCenter/rpc/internal/common"
+	"app.yhyue.com/moapp/jybase/common"
 	"context"
 	"github.com/gogf/gf/v2/util/gconv"
+	"path"
+	"strings"
 
 	"app.yhyue.com/moapp/MessageCenter/rpc/internal/svc"
 	"app.yhyue.com/moapp/MessageCenter/rpc/type/message"
 
+	fpb "bp.jydev.jianyu360.cn/BaseService/fileCenter/rpc/pb"
 	"github.com/zeromicro/go-zero/core/logx"
 )
 
@@ -31,6 +36,46 @@ func (l *MlDetailsLogic) MlDetails(in *message.MaterialDetailReq) (*message.Mate
 	//先查询详情
 	data := service.MaterialDetail(in.MsgLogId, in.PositionId)
 	if data != nil {
+		attUrl := []string{}
+		qrCodeUrl := common.InterfaceToStr(data["qrcode_url"])
+		fileUrl := common.InterfaceToStr(data["file_url"])
+		if qrCodeUrl != "" && common.InterfaceToStr(data["attUrl"]) == "" && len(strings.Split(fileUrl, ",")) != 0 {
+			for _, val := range strings.Split(fileUrl, ",") {
+				imgByte, err := service.PersonImgSaveComposite(val, qrCodeUrl, "", in.PositionId)
+				if err != nil {
+					return &message.MaterialDetailResp{
+						Code:    1,
+						Message: "获取详情成功",
+						Data:    nil,
+					}, nil
+				}
+				up, err := entity.FileCenterRpc.Upload(l.ctx, &fpb.UploadReq{
+					File:           imgByte,
+					OssBucketName:  entity.OssBucketName,
+					OssUrl:         entity.OssUrl,
+					Name:           path.Base(fileUrl),
+					NeedEncryption: false,
+				})
+				if up == nil || up.Url == "" {
+					return &message.MaterialDetailResp{
+						Code:    -1,
+						Message: "上传合成图片失败",
+						Data:    nil,
+					}, nil
+				}
+				attUrl = append(attUrl, up.Url)
+				ok := service.PersonImageSave(up.Url, common.Int64All(data["id"]), in.PositionId, common.Int64All(data["mid"]))
+				if !ok {
+					return &message.MaterialDetailResp{
+						Code:    -1,
+						Message: "保存个人图片失败",
+						Data:    nil,
+					}, nil
+				}
+			}
+			//}
+			data["attUrl"] = strings.Join(attUrl, ",")
+		}
 		return &message.MaterialDetailResp{
 			Code:    1,
 			Message: "获取详情成功",

+ 24 - 0
rpc/message.go

@@ -16,6 +16,7 @@ import (
 	m "app.yhyue.com/moapp/jybase/mongodb"
 	"app.yhyue.com/moapp/jybase/mysql"
 	"app.yhyue.com/moapp/jybase/redis"
+	"bp.jydev.jianyu360.cn/BaseService/fileCenter/rpc/filecenter"
 	"context"
 	"flag"
 	"fmt"
@@ -50,6 +51,9 @@ func main() {
 	s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
 		message.RegisterMessageServer(grpcServer, srv)
 	})
+	entity.FileCenterRpc = filecenter.NewFileCenter(zrpc.MustNewClient(config.ConfigJson.FileCenterRpc))
+	entity.OssBucketName = config.ConfigJson.OssBucketName
+	entity.OssUrl = config.ConfigJson.OssUrl
 	defer s.Stop()
 	fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)
 	s.Start()
@@ -76,6 +80,26 @@ func init() {
 	}
 	entity.Mysql.Init()
 
+	entity.BiService = &mysql.Mysql{
+		Address:      config.ConfigJson.BiService.Address,
+		UserName:     config.ConfigJson.BiService.UserName,
+		PassWord:     config.ConfigJson.BiService.PassWord,
+		DBName:       config.ConfigJson.BiService.DbName,
+		MaxOpenConns: config.ConfigJson.BiService.MaxOpenConns,
+		MaxIdleConns: config.ConfigJson.BiService.MaxIdleConns,
+	}
+	entity.BiService.Init()
+
+	entity.JyBi = &mysql.Mysql{
+		Address:      config.ConfigJson.JySubject.Address,
+		UserName:     config.ConfigJson.JySubject.UserName,
+		PassWord:     config.ConfigJson.JySubject.PassWord,
+		DBName:       config.ConfigJson.JySubject.DbName,
+		MaxOpenConns: config.ConfigJson.JySubject.MaxOpenConns,
+		MaxIdleConns: config.ConfigJson.JySubject.MaxIdleConns,
+	}
+	entity.JyBi.Init()
+
 	common.AllMsgType, entity.MessageColumn = common.MessageType()
 	// 初始化需要展示消息细化分类的groupid 和对应的class信息
 	common.InitClassSearchMap()