🐳 add docker file

This commit is contained in:
2025-01-23 15:24:59 +08:00
parent c6af9a0461
commit fc95d73adb
27 changed files with 5117 additions and 269 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -4,15 +4,35 @@ package ai;
option go_package = "./pb";
// 人脸识别
message FaceRecognitionRequest {
bytes face = 1;
string user_id = 2;
}
message FaceRecognitionResponse {
int64 face_id = 1;
int64 face_id = 3;
}
// tf分类识别
message TfClassificationRequest {
bytes image = 1;
}
message TfClassificationResponse {
string class_name = 2;
float score = 3;
}
// caffe分类识别
message CaffeClassificationRequest {
bytes image = 1;
}
message CaffeClassificationResponse {
string class_name = 2;
float score = 3;
}
service AiService {
// FaceRecognition
rpc FaceRecognition (FaceRecognitionRequest) returns (FaceRecognitionResponse);
// TfClassification
rpc TfClassification (TfClassificationRequest) returns (TfClassificationResponse);
// CaffeClassification
rpc CaffeClassification (CaffeClassificationRequest) returns (CaffeClassificationResponse);
}

View File

@@ -14,12 +14,20 @@ import (
)
type (
FaceRecognitionRequest = pb.FaceRecognitionRequest
FaceRecognitionResponse = pb.FaceRecognitionResponse
CaffeClassificationRequest = pb.CaffeClassificationRequest
CaffeClassificationResponse = pb.CaffeClassificationResponse
FaceRecognitionRequest = pb.FaceRecognitionRequest
FaceRecognitionResponse = pb.FaceRecognitionResponse
TfClassificationRequest = pb.TfClassificationRequest
TfClassificationResponse = pb.TfClassificationResponse
AiService interface {
// FaceRecognition
FaceRecognition(ctx context.Context, in *FaceRecognitionRequest, opts ...grpc.CallOption) (*FaceRecognitionResponse, error)
// TfClassification
TfClassification(ctx context.Context, in *TfClassificationRequest, opts ...grpc.CallOption) (*TfClassificationResponse, error)
// CaffeClassification
CaffeClassification(ctx context.Context, in *CaffeClassificationRequest, opts ...grpc.CallOption) (*CaffeClassificationResponse, error)
}
defaultAiService struct {
@@ -38,3 +46,15 @@ func (m *defaultAiService) FaceRecognition(ctx context.Context, in *FaceRecognit
client := pb.NewAiServiceClient(m.cli.Conn())
return client.FaceRecognition(ctx, in, opts...)
}
// TfClassification
func (m *defaultAiService) TfClassification(ctx context.Context, in *TfClassificationRequest, opts ...grpc.CallOption) (*TfClassificationResponse, error) {
client := pb.NewAiServiceClient(m.cli.Conn())
return client.TfClassification(ctx, in, opts...)
}
// CaffeClassification
func (m *defaultAiService) CaffeClassification(ctx context.Context, in *CaffeClassificationRequest, opts ...grpc.CallOption) (*CaffeClassificationResponse, error) {
client := pb.NewAiServiceClient(m.cli.Conn())
return client.CaffeClassification(ctx, in, opts...)
}

View File

@@ -1,9 +1,11 @@
Name: aisvc.rpc
ListenOn: 0.0.0.0:8080
ListenOn: 0.0.0.0:8888
Etcd:
Hosts:
- 127.0.0.1:2379
- 1.95.0.111:2379
Key: aisvc.rpc
User: root
Pass: LDQ20020618xxx
# MySQL 配置
Mysql:
# 数据源dsn

View File

@@ -0,0 +1,31 @@
package aiservicelogic
import (
"context"
"schisandra-album-cloud-microservices/app/aisvc/rpc/internal/svc"
"schisandra-album-cloud-microservices/app/aisvc/rpc/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type CaffeClassificationLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewCaffeClassificationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CaffeClassificationLogic {
return &CaffeClassificationLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// CaffeClassification
func (l *CaffeClassificationLogic) CaffeClassification(in *pb.CaffeClassificationRequest) (*pb.CaffeClassificationResponse, error) {
// todo: add your logic here and delete this line
return &pb.CaffeClassificationResponse{}, nil
}

View File

@@ -43,8 +43,12 @@ func NewFaceRecognitionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *F
// FaceRecognition 人脸识别
func (l *FaceRecognitionLogic) FaceRecognition(in *pb.FaceRecognitionRequest) (*pb.FaceRecognitionResponse, error) {
toJPEG, err := l.ConvertImageToJPEG(in.GetFace())
if err != nil {
return nil, err
}
// 提取人脸特征
faceFeatures, err := l.svcCtx.FaceRecognizer.RecognizeSingle(in.GetFace())
faceFeatures, err := l.svcCtx.FaceRecognizer.RecognizeSingle(toJPEG)
if err != nil {
return nil, err
}
@@ -82,7 +86,7 @@ func (l *FaceRecognitionLogic) FaceRecognition(in *pb.FaceRecognitionRequest) (*
// 人脸分类
classify := l.svcCtx.FaceRecognizer.ClassifyThreshold(faceFeatures.Descriptor, 0.6)
if classify >= 0 {
if classify >= 0 && classify < len(ids) {
return &pb.FaceRecognitionResponse{
FaceId: int64(ids[classify]),
}, nil
@@ -92,6 +96,26 @@ func (l *FaceRecognitionLogic) FaceRecognition(in *pb.FaceRecognitionRequest) (*
return l.saveNewFace(in, faceFeatures, hashKey)
}
// ConvertImageToJPEG 将非 JPEG 格式的图片字节数据转换为 JPEG
func (l *FaceRecognitionLogic) ConvertImageToJPEG(imageData []byte) ([]byte, error) {
// 使用 image.Decode 解码图像数据
img, _, err := image.Decode(bytes.NewReader(imageData))
if err != nil {
return nil, fmt.Errorf("failed to decode image: %v", err)
}
// 创建一个缓冲区来存储 JPEG 格式的数据
var jpegBuffer bytes.Buffer
// 将图片编码为 JPEG 格式
err = jpeg.Encode(&jpegBuffer, img, nil)
if err != nil {
return nil, fmt.Errorf("failed to encode image to JPEG: %v", err)
}
return jpegBuffer.Bytes(), nil
}
// 保存新的人脸样本到数据库和 Redis
func (l *FaceRecognitionLogic) saveNewFace(in *pb.FaceRecognitionRequest, faceFeatures *face.Face, hashKey string) (*pb.FaceRecognitionResponse, error) {
// 人脸有效性判断 (大小必须大于50)

View File

@@ -0,0 +1,79 @@
package aiservicelogic
import (
"context"
"fmt"
"gocv.io/x/gocv"
"image"
"schisandra-album-cloud-microservices/app/aisvc/rpc/internal/svc"
"schisandra-album-cloud-microservices/app/aisvc/rpc/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type TfClassificationLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewTfClassificationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *TfClassificationLogic {
return &TfClassificationLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// TfClassification is a server endpoint to classify an image using TensorFlow.
func (l *TfClassificationLogic) TfClassification(in *pb.TfClassificationRequest) (*pb.TfClassificationResponse, error) {
className, source, err := l.ClassifyImage(in.GetImage())
if err != nil {
return nil, err
}
return &pb.TfClassificationResponse{
Score: source,
ClassName: className,
}, nil
}
// ClassifyImage 从字节数据分类图像,返回分类标签和最大概率值
func (l *TfClassificationLogic) ClassifyImage(imageBytes []byte) (string, float32, error) {
// 解码字节数据为图像
img, err := gocv.IMDecode(imageBytes, gocv.IMReadColor)
if err != nil || img.Empty() {
return "", 0, fmt.Errorf("failed to decode image: %v", err)
}
defer func(img *gocv.Mat) {
_ = img.Close()
}(&img)
// 将图像 Mat 转换为 224x224 blob以便分类器分析
blob := gocv.BlobFromImage(img, 1.0, image.Pt(224, 224), gocv.NewScalar(0, 0, 0, 0), true, false)
// 将 blob 输入分类器
l.svcCtx.TfNet.SetInput(blob, "input")
// 运行网络的正向传递
prob := l.svcCtx.TfNet.Forward("softmax2")
// 将结果重塑为 1x1000 矩阵
probMat := prob.Reshape(1, 1)
// 确定最可能的分类
_, maxVal, _, maxLoc := gocv.MinMaxLoc(probMat)
// 获取分类描述
desc := ""
if maxLoc.X < 1000 {
desc = l.svcCtx.TfDesc[maxLoc.X]
}
// 清理资源
_ = blob.Close()
_ = prob.Close()
_ = probMat.Close()
return desc, maxVal, nil
}

View File

@@ -28,3 +28,15 @@ func (s *AiServiceServer) FaceRecognition(ctx context.Context, in *pb.FaceRecogn
l := aiservicelogic.NewFaceRecognitionLogic(ctx, s.svcCtx)
return l.FaceRecognition(in)
}
// TfClassification
func (s *AiServiceServer) TfClassification(ctx context.Context, in *pb.TfClassificationRequest) (*pb.TfClassificationResponse, error) {
l := aiservicelogic.NewTfClassificationLogic(ctx, s.svcCtx)
return l.TfClassification(in)
}
// CaffeClassification
func (s *AiServiceServer) CaffeClassification(ctx context.Context, in *pb.CaffeClassificationRequest) (*pb.CaffeClassificationResponse, error) {
l := aiservicelogic.NewCaffeClassificationLogic(ctx, s.svcCtx)
return l.CaffeClassification(in)
}

View File

@@ -3,11 +3,14 @@ package svc
import (
"github.com/Kagami/go-face"
"github.com/redis/go-redis/v9"
"gocv.io/x/gocv"
"schisandra-album-cloud-microservices/app/aisvc/model/mysql"
"schisandra-album-cloud-microservices/app/aisvc/model/mysql/query"
"schisandra-album-cloud-microservices/app/aisvc/rpc/internal/config"
"schisandra-album-cloud-microservices/common/caffe_classifier"
"schisandra-album-cloud-microservices/common/face_recognizer"
"schisandra-album-cloud-microservices/common/redisx"
"schisandra-album-cloud-microservices/common/tf_classifier"
)
type ServiceContext struct {
@@ -15,15 +18,25 @@ type ServiceContext struct {
FaceRecognizer *face.Recognizer
DB *query.Query
RedisClient *redis.Client
TfNet *gocv.Net
TfDesc []string
CaffeNet *gocv.Net
CaffeDesc []string
}
func NewServiceContext(c config.Config) *ServiceContext {
redisClient := redisx.NewRedis(c.RedisConf.Host, c.RedisConf.Pass, c.RedisConf.DB)
_, queryDB := mysql.NewMySQL(c.Mysql.DataSource, c.Mysql.MaxOpenConn, c.Mysql.MaxIdleConn, redisClient)
tfClassifier, tfDesc := tf_classifier.NewTFClassifier()
caffeClassifier, caffeDesc := caffe_classifier.NewCaffeClassifier()
return &ServiceContext{
Config: c,
FaceRecognizer: face_recognizer.NewFaceRecognition(),
DB: queryDB,
RedisClient: redisClient,
TfNet: tfClassifier,
TfDesc: tfDesc,
CaffeNet: caffeClassifier,
CaffeDesc: caffeDesc,
}
}

View File

@@ -20,6 +20,7 @@ const (
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// 人脸识别
type FaceRecognitionRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -78,7 +79,7 @@ type FaceRecognitionResponse struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
FaceId int64 `protobuf:"varint,1,opt,name=face_id,json=faceId,proto3" json:"face_id,omitempty"`
FaceId int64 `protobuf:"varint,3,opt,name=face_id,json=faceId,proto3" json:"face_id,omitempty"`
}
func (x *FaceRecognitionResponse) Reset() {
@@ -118,6 +119,204 @@ func (x *FaceRecognitionResponse) GetFaceId() int64 {
return 0
}
// tf分类识别
type TfClassificationRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Image []byte `protobuf:"bytes,1,opt,name=image,proto3" json:"image,omitempty"`
}
func (x *TfClassificationRequest) Reset() {
*x = TfClassificationRequest{}
mi := &file_aisvc_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *TfClassificationRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TfClassificationRequest) ProtoMessage() {}
func (x *TfClassificationRequest) ProtoReflect() protoreflect.Message {
mi := &file_aisvc_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TfClassificationRequest.ProtoReflect.Descriptor instead.
func (*TfClassificationRequest) Descriptor() ([]byte, []int) {
return file_aisvc_proto_rawDescGZIP(), []int{2}
}
func (x *TfClassificationRequest) GetImage() []byte {
if x != nil {
return x.Image
}
return nil
}
type TfClassificationResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ClassName string `protobuf:"bytes,2,opt,name=class_name,json=className,proto3" json:"class_name,omitempty"`
Score float32 `protobuf:"fixed32,3,opt,name=score,proto3" json:"score,omitempty"`
}
func (x *TfClassificationResponse) Reset() {
*x = TfClassificationResponse{}
mi := &file_aisvc_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *TfClassificationResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TfClassificationResponse) ProtoMessage() {}
func (x *TfClassificationResponse) ProtoReflect() protoreflect.Message {
mi := &file_aisvc_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TfClassificationResponse.ProtoReflect.Descriptor instead.
func (*TfClassificationResponse) Descriptor() ([]byte, []int) {
return file_aisvc_proto_rawDescGZIP(), []int{3}
}
func (x *TfClassificationResponse) GetClassName() string {
if x != nil {
return x.ClassName
}
return ""
}
func (x *TfClassificationResponse) GetScore() float32 {
if x != nil {
return x.Score
}
return 0
}
// caffe分类识别
type CaffeClassificationRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Image []byte `protobuf:"bytes,1,opt,name=image,proto3" json:"image,omitempty"`
}
func (x *CaffeClassificationRequest) Reset() {
*x = CaffeClassificationRequest{}
mi := &file_aisvc_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *CaffeClassificationRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CaffeClassificationRequest) ProtoMessage() {}
func (x *CaffeClassificationRequest) ProtoReflect() protoreflect.Message {
mi := &file_aisvc_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CaffeClassificationRequest.ProtoReflect.Descriptor instead.
func (*CaffeClassificationRequest) Descriptor() ([]byte, []int) {
return file_aisvc_proto_rawDescGZIP(), []int{4}
}
func (x *CaffeClassificationRequest) GetImage() []byte {
if x != nil {
return x.Image
}
return nil
}
type CaffeClassificationResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ClassName string `protobuf:"bytes,2,opt,name=class_name,json=className,proto3" json:"class_name,omitempty"`
Score float32 `protobuf:"fixed32,3,opt,name=score,proto3" json:"score,omitempty"`
}
func (x *CaffeClassificationResponse) Reset() {
*x = CaffeClassificationResponse{}
mi := &file_aisvc_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *CaffeClassificationResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CaffeClassificationResponse) ProtoMessage() {}
func (x *CaffeClassificationResponse) ProtoReflect() protoreflect.Message {
mi := &file_aisvc_proto_msgTypes[5]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CaffeClassificationResponse.ProtoReflect.Descriptor instead.
func (*CaffeClassificationResponse) Descriptor() ([]byte, []int) {
return file_aisvc_proto_rawDescGZIP(), []int{5}
}
func (x *CaffeClassificationResponse) GetClassName() string {
if x != nil {
return x.ClassName
}
return ""
}
func (x *CaffeClassificationResponse) GetScore() float32 {
if x != nil {
return x.Score
}
return 0
}
var File_aisvc_proto protoreflect.FileDescriptor
var file_aisvc_proto_rawDesc = []byte{
@@ -128,15 +327,42 @@ var file_aisvc_proto_rawDesc = []byte{
0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x32, 0x0a, 0x17, 0x46, 0x61, 0x63, 0x65,
0x52, 0x65, 0x63, 0x6f, 0x67, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x66, 0x61, 0x63, 0x65, 0x49, 0x64, 0x32, 0x57, 0x0a, 0x09,
0x41, 0x69, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4a, 0x0a, 0x0f, 0x46, 0x61, 0x63,
0x65, 0x52, 0x65, 0x63, 0x6f, 0x67, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x2e, 0x61,
0x69, 0x2e, 0x46, 0x61, 0x63, 0x65, 0x52, 0x65, 0x63, 0x6f, 0x67, 0x6e, 0x69, 0x74, 0x69, 0x6f,
0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x61, 0x69, 0x2e, 0x46, 0x61,
0x63, 0x65, 0x52, 0x65, 0x63, 0x6f, 0x67, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x06, 0x5a, 0x04, 0x2e, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x33,
0x6e, 0x73, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03,
0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x66, 0x61, 0x63, 0x65, 0x49, 0x64, 0x22, 0x2f, 0x0a, 0x17,
0x54, 0x66, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x22, 0x4f, 0x0a,
0x18, 0x54, 0x66, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x61,
0x73, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63,
0x6c, 0x61, 0x73, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x72,
0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x22, 0x32,
0x0a, 0x1a, 0x43, 0x61, 0x66, 0x66, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x69, 0x66, 0x69, 0x63,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05,
0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x69, 0x6d, 0x61,
0x67, 0x65, 0x22, 0x52, 0x0a, 0x1b, 0x43, 0x61, 0x66, 0x66, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73,
0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x4e, 0x61, 0x6d, 0x65,
0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52,
0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x32, 0xfe, 0x01, 0x0a, 0x09, 0x41, 0x69, 0x53, 0x65, 0x72,
0x76, 0x69, 0x63, 0x65, 0x12, 0x4a, 0x0a, 0x0f, 0x46, 0x61, 0x63, 0x65, 0x52, 0x65, 0x63, 0x6f,
0x67, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x2e, 0x61, 0x69, 0x2e, 0x46, 0x61, 0x63,
0x65, 0x52, 0x65, 0x63, 0x6f, 0x67, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x61, 0x69, 0x2e, 0x46, 0x61, 0x63, 0x65, 0x52, 0x65, 0x63,
0x6f, 0x67, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x4d, 0x0a, 0x10, 0x54, 0x66, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x69, 0x66, 0x69, 0x63, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x2e, 0x61, 0x69, 0x2e, 0x54, 0x66, 0x43, 0x6c, 0x61, 0x73,
0x73, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x1c, 0x2e, 0x61, 0x69, 0x2e, 0x54, 0x66, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x69, 0x66,
0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x56, 0x0a, 0x13, 0x43, 0x61, 0x66, 0x66, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x69, 0x66, 0x69,
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x2e, 0x61, 0x69, 0x2e, 0x43, 0x61, 0x66, 0x66,
0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x61, 0x69, 0x2e, 0x43, 0x61, 0x66, 0x66,
0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x06, 0x5a, 0x04, 0x2e, 0x2f, 0x70, 0x62, 0x62,
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -151,16 +377,24 @@ func file_aisvc_proto_rawDescGZIP() []byte {
return file_aisvc_proto_rawDescData
}
var file_aisvc_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_aisvc_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
var file_aisvc_proto_goTypes = []any{
(*FaceRecognitionRequest)(nil), // 0: ai.FaceRecognitionRequest
(*FaceRecognitionResponse)(nil), // 1: ai.FaceRecognitionResponse
(*FaceRecognitionRequest)(nil), // 0: ai.FaceRecognitionRequest
(*FaceRecognitionResponse)(nil), // 1: ai.FaceRecognitionResponse
(*TfClassificationRequest)(nil), // 2: ai.TfClassificationRequest
(*TfClassificationResponse)(nil), // 3: ai.TfClassificationResponse
(*CaffeClassificationRequest)(nil), // 4: ai.CaffeClassificationRequest
(*CaffeClassificationResponse)(nil), // 5: ai.CaffeClassificationResponse
}
var file_aisvc_proto_depIdxs = []int32{
0, // 0: ai.AiService.FaceRecognition:input_type -> ai.FaceRecognitionRequest
1, // 1: ai.AiService.FaceRecognition:output_type -> ai.FaceRecognitionResponse
1, // [1:2] is the sub-list for method output_type
0, // [0:1] is the sub-list for method input_type
2, // 1: ai.AiService.TfClassification:input_type -> ai.TfClassificationRequest
4, // 2: ai.AiService.CaffeClassification:input_type -> ai.CaffeClassificationRequest
1, // 3: ai.AiService.FaceRecognition:output_type -> ai.FaceRecognitionResponse
3, // 4: ai.AiService.TfClassification:output_type -> ai.TfClassificationResponse
5, // 5: ai.AiService.CaffeClassification:output_type -> ai.CaffeClassificationResponse
3, // [3:6] is the sub-list for method output_type
0, // [0:3] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
@@ -177,7 +411,7 @@ func file_aisvc_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_aisvc_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumMessages: 6,
NumExtensions: 0,
NumServices: 1,
},

View File

@@ -19,7 +19,9 @@ import (
const _ = grpc.SupportPackageIsVersion9
const (
AiService_FaceRecognition_FullMethodName = "/ai.AiService/FaceRecognition"
AiService_FaceRecognition_FullMethodName = "/ai.AiService/FaceRecognition"
AiService_TfClassification_FullMethodName = "/ai.AiService/TfClassification"
AiService_CaffeClassification_FullMethodName = "/ai.AiService/CaffeClassification"
)
// AiServiceClient is the client API for AiService service.
@@ -28,6 +30,10 @@ const (
type AiServiceClient interface {
// FaceRecognition
FaceRecognition(ctx context.Context, in *FaceRecognitionRequest, opts ...grpc.CallOption) (*FaceRecognitionResponse, error)
// TfClassification
TfClassification(ctx context.Context, in *TfClassificationRequest, opts ...grpc.CallOption) (*TfClassificationResponse, error)
// CaffeClassification
CaffeClassification(ctx context.Context, in *CaffeClassificationRequest, opts ...grpc.CallOption) (*CaffeClassificationResponse, error)
}
type aiServiceClient struct {
@@ -48,12 +54,36 @@ func (c *aiServiceClient) FaceRecognition(ctx context.Context, in *FaceRecogniti
return out, nil
}
func (c *aiServiceClient) TfClassification(ctx context.Context, in *TfClassificationRequest, opts ...grpc.CallOption) (*TfClassificationResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(TfClassificationResponse)
err := c.cc.Invoke(ctx, AiService_TfClassification_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *aiServiceClient) CaffeClassification(ctx context.Context, in *CaffeClassificationRequest, opts ...grpc.CallOption) (*CaffeClassificationResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(CaffeClassificationResponse)
err := c.cc.Invoke(ctx, AiService_CaffeClassification_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// AiServiceServer is the server API for AiService service.
// All implementations must embed UnimplementedAiServiceServer
// for forward compatibility.
type AiServiceServer interface {
// FaceRecognition
FaceRecognition(context.Context, *FaceRecognitionRequest) (*FaceRecognitionResponse, error)
// TfClassification
TfClassification(context.Context, *TfClassificationRequest) (*TfClassificationResponse, error)
// CaffeClassification
CaffeClassification(context.Context, *CaffeClassificationRequest) (*CaffeClassificationResponse, error)
mustEmbedUnimplementedAiServiceServer()
}
@@ -67,6 +97,12 @@ type UnimplementedAiServiceServer struct{}
func (UnimplementedAiServiceServer) FaceRecognition(context.Context, *FaceRecognitionRequest) (*FaceRecognitionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method FaceRecognition not implemented")
}
func (UnimplementedAiServiceServer) TfClassification(context.Context, *TfClassificationRequest) (*TfClassificationResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method TfClassification not implemented")
}
func (UnimplementedAiServiceServer) CaffeClassification(context.Context, *CaffeClassificationRequest) (*CaffeClassificationResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CaffeClassification not implemented")
}
func (UnimplementedAiServiceServer) mustEmbedUnimplementedAiServiceServer() {}
func (UnimplementedAiServiceServer) testEmbeddedByValue() {}
@@ -106,6 +142,42 @@ func _AiService_FaceRecognition_Handler(srv interface{}, ctx context.Context, de
return interceptor(ctx, in, info, handler)
}
func _AiService_TfClassification_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(TfClassificationRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AiServiceServer).TfClassification(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: AiService_TfClassification_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AiServiceServer).TfClassification(ctx, req.(*TfClassificationRequest))
}
return interceptor(ctx, in, info, handler)
}
func _AiService_CaffeClassification_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CaffeClassificationRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AiServiceServer).CaffeClassification(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: AiService_CaffeClassification_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AiServiceServer).CaffeClassification(ctx, req.(*CaffeClassificationRequest))
}
return interceptor(ctx, in, info, handler)
}
// AiService_ServiceDesc is the grpc.ServiceDesc for AiService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@@ -117,6 +189,14 @@ var AiService_ServiceDesc = grpc.ServiceDesc{
MethodName: "FaceRecognition",
Handler: _AiService_FaceRecognition_Handler,
},
{
MethodName: "TfClassification",
Handler: _AiService_TfClassification_Handler,
},
{
MethodName: "CaffeClassification",
Handler: _AiService_CaffeClassification_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "aisvc.proto",

View File

@@ -433,7 +433,7 @@ type (
@server (
group: storage // 微服务分组
prefix: /api/auth/storage // 微服务前缀
timeout: 10s // 超时时间
timeout: 20s // 超时时间
maxBytes: 104857600 // 最大请求大小
signature: false // 是否开启签名验证
middleware: SecurityHeadersMiddleware,CasbinVerifyMiddleware,AuthorizationMiddleware,NonceMiddleware // 注册中间件

View File

@@ -22,8 +22,10 @@ Verbose: false
AiSvcRpc:
Etcd:
Hosts:
- 127.0.0.1:2379
- 1.95.0.111:2379
Key: aisvc.rpc
User: root
Pass: LDQ20020618xxx
# 日志配置
Log:
# 服务名称

View File

@@ -202,7 +202,7 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
),
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
rest.WithPrefix("/api/auth/storage"),
rest.WithTimeout(10000*time.Millisecond),
rest.WithTimeout(20000*time.Millisecond),
rest.WithMaxBytes(104857600),
)

View File

@@ -69,6 +69,13 @@ func (l *UploadFileLogic) UploadFile(r *http.Request) (resp string, err error) {
if face != nil {
faceId = face.GetFaceId()
}
// 分类
classification, err := l.svcCtx.AiSvcRpc.TfClassification(l.ctx, &pb.TfClassificationRequest{Image: bytes})
if err != nil {
return "", err
}
fmt.Println(classification.ClassName)
fmt.Println(classification.Score)
// 解析 EXIF 信息
exif, err := l.parseExifData(result.Exif)

View File

@@ -1,120 +0,0 @@
# to build this docker image:
# docker build -f opencv.Dockerfile -t schisandra-cloud-album-server .
# docker build --build-arg OPENCV_VERSION="4.x" --build-arg OPENCV_FILE="https://github.com/opencv/opencv/archive/refs/heads/4.x.zip" --build-arg OPENCV_CONTRIB_FILE="https://github.com/opencv/opencv_contrib/archive/refs/heads/4.x.zip" -f opencv.Dockerfile -t schisandra-cloud-album-server .
FROM ubuntu:20.04 AS opencv-builder
LABEL maintainer="landaiqing <<landaiqing@126.com>>"
RUN sed -i 's|http://archive.ubuntu.com/ubuntu/|http://mirrors.aliyun.com/ubuntu/|g' /etc/apt/sources.list
ENV TZ=Europe/Madrid
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN apt-get update && apt-get install -y --no-install-recommends --fix-missing \
tzdata git build-essential cmake pkg-config wget unzip libgtk2.0-dev \
curl ca-certificates libcurl4-openssl-dev libssl-dev \
libavcodec-dev libavformat-dev libswscale-dev libtbb2 libtbb-dev \
libharfbuzz-dev libfreetype6-dev \
libjpeg-turbo8-dev libpng-dev libtiff-dev libdc1394-22-dev nasm && \
rm -rf /var/lib/apt/lists/*
ARG OPENCV_VERSION="4.10.0"
ENV OPENCV_VERSION=$OPENCV_VERSION
ARG OPENCV_FILE="https://github.com/opencv/opencv/archive/${OPENCV_VERSION}.zip"
ENV OPENCV_FILE=$OPENCV_FILE
ARG OPENCV_CONTRIB_FILE="https://github.com/opencv/opencv_contrib/archive/${OPENCV_VERSION}.zip"
ENV OPENCV_CONTRIB_FILE=$OPENCV_CONTRIB_FILE
RUN curl -Lo opencv.zip ${OPENCV_FILE} && \
unzip -q opencv.zip && \
curl -Lo opencv_contrib.zip ${OPENCV_CONTRIB_FILE} && \
unzip -q opencv_contrib.zip && \
rm opencv.zip opencv_contrib.zip && \
cd opencv-${OPENCV_VERSION} && \
mkdir build && cd build && \
cmake -D CMAKE_BUILD_TYPE=RELEASE \
-D WITH_IPP=OFF \
-D WITH_OPENGL=OFF \
-D WITH_QT=OFF \
-D WITH_FREETYPE=ON \
-D CMAKE_INSTALL_PREFIX=/usr/local \
-D OPENCV_EXTRA_MODULES_PATH=../../opencv_contrib-${OPENCV_VERSION}/modules \
-D OPENCV_ENABLE_NONFREE=ON \
-D WITH_JASPER=OFF \
-D WITH_TBB=ON \
-D BUILD_JPEG=ON \
-D WITH_SIMD=ON \
-D ENABLE_LIBJPEG_TURBO_SIMD=ON \
-D BUILD_DOCS=OFF \
-D BUILD_EXAMPLES=OFF \
-D BUILD_TESTS=OFF \
-D BUILD_PERF_TESTS=ON \
-D BUILD_opencv_java=NO \
-D BUILD_opencv_python=NO \
-D BUILD_opencv_python2=NO \
-D BUILD_opencv_python3=NO \
-D OPENCV_GENERATE_PKGCONFIG=ON .. && \
make -j $(nproc --all) && \
make preinstall && make install && ldconfig && \
cd / && rm -rf opencv*
FROM golang:1.23.1-alpine AS go-builder
RUN apk add --no-cache gcc musl-dev libgcc libstdc++ cmake
WORKDIR /app
COPY . .
ENV CGO_ENABLED=1
ENV CGO_CFLAGS="-I/usr/local/include/opencv4"
ENV CGO_LDFLAGS="-L/usr/local/lib -lopencv_core -lopencv_imgproc -lopencv_highgui"
ENV GOOS=linux
ENV GOARCH=amd64
ENV GOPROXY=https://goproxy.cn,direct
COPY --from=opencv-builder /usr/local/lib /usr/local/lib
COPY --from=opencv-builder /usr/local/include/opencv4 /usr/local/include/opencv4
RUN go mod download
RUN go build -o schisandra-cloud-album-server
FROM alpine:latest
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
RUN apk add --no-cache tzdata
ENV TZ=Asia/Shanghai
WORKDIR /app
COPY --from=go-builder /app/schisandra-cloud-album-server .
COPY --from=go-builder /app/config.yaml .
COPY --from=go-builder /app/resource ./resource
COPY --from=go-builder /app/config/rbac_model.conf ./config/rbac_model.conf
COPY --from=opencv-builder /usr/local/lib /usr/local/lib
COPY --from=opencv-builder /usr/local/include/opencv4 /usr/local/include/opencv4
EXPOSE 80
CMD ["./schisandra-cloud-album-server"]