add face recognition

This commit is contained in:
2025-01-22 10:36:28 +08:00
parent eab806fb9b
commit c6af9a0461
47 changed files with 3621 additions and 454 deletions

View File

@@ -0,0 +1,107 @@
package main
import (
"os"
"path/filepath"
"strings"
"gorm.io/driver/mysql"
"gorm.io/gen"
"gorm.io/gen/field"
"gorm.io/gorm"
)
const MySQLDSN = "root:LDQ20020618xxx@tcp(1.95.0.111:3306)/schisandra-cloud-album?charset=utf8mb4&parseTime=True&loc=Local"
func main() {
// 连接数据库
db, err := gorm.Open(mysql.Open(MySQLDSN))
if err != nil {
panic(err)
}
dir, err := os.Getwd()
if err != nil {
panic(err)
}
path := filepath.Join(dir, "app/aisvc/model/mysql/", "query")
// 生成实例
g := gen.NewGenerator(gen.Config{
// 相对执行`go run`时的路径, 会自动创建目录
OutPath: path,
// 生成的文件名默认gen.go
OutFile: "gen.go",
// 生成DAO代码的包名默认是model
ModelPkgPath: "model",
// 是否为DAO包生成单元测试代码默认false
WithUnitTest: false,
// WithDefaultQuery 生成默认查询结构体(作为全局变量使用), 即`Q`结构体和其字段(各表模型)
// WithoutContext 生成没有context调用限制的代码供查询
// WithQueryInterface 生成interface形式的查询代码(可导出), 如`Where()`方法返回的就是一个可导出的接口类型
Mode: gen.WithDefaultQuery | gen.WithQueryInterface | gen.WithoutContext,
// 表字段可为 null 值时, 对应结体字段使用指针类型
FieldNullable: false, // generate pointer when field is nullable
// 表字段默认值与模型结构体字段零值不一致的字段, 在插入数据时需要赋值该字段值为零值的, 结构体字段须是指针类型才能成功, 即`FieldCoverable:true`配置下生成的结构体字段.
// 因为在插入时遇到字段为零值的会被GORM赋予默认值. 如字段`age`表默认值为10, 即使你显式设置为0最后也会被GORM设为10提交.
// 如果该字段没有上面提到的插入时赋零值的特殊需要, 则字段为非指针类型使用起来会比较方便.
FieldCoverable: true,
// 模型结构体字段的数字类型的符号表示是否与表字段的一致, `false`指示都用有符号类型
FieldSignable: false,
// 生成 gorm 标签的字段索引属性
FieldWithIndexTag: true,
// 生成 gorm 标签的字段类型属性
FieldWithTypeTag: true,
})
// 设置目标 db
g.UseDB(db)
// 自定义字段的数据类型
// 统一数字类型为int64,兼容protobuf
dataMap := map[string]func(columnType gorm.ColumnType) (dataType string){
"tinyint": func(columnType gorm.ColumnType) (dataType string) { return "int64" },
"smallint": func(columnType gorm.ColumnType) (dataType string) { return "int64" },
"mediumint": func(columnType gorm.ColumnType) (dataType string) { return "int64" },
"bigint": func(columnType gorm.ColumnType) (dataType string) { return "int64" },
"int": func(columnType gorm.ColumnType) (dataType string) { return "int64" },
}
// 要先于`ApplyBasic`执行
g.WithDataTypeMap(dataMap)
// 自定义模型结体字段的标签
// 将特定字段名的 json 标签加上`string`属性,即 MarshalJSON 时该字段由数字类型转成字符串类型
jsonField := gen.FieldJSONTagWithNS(func(columnName string) (tagContent string) {
toStringField := `id, `
if strings.Contains(toStringField, columnName) {
return columnName + ",string"
}
return columnName
})
// 将非默认字段名的字段定义为自动时间戳和软删除字段;
// 自动时间戳默认字段名为:`updated_at`、`created_at, 表字段数据类型为: INT 或 DATETIME
// 软删除默认字段名为:`deleted_at`, 表字段数据类型为: DATETIME
idField := gen.FieldGORMTag("id", func(tag field.GormTag) field.GormTag {
return tag.Append("primary_key")
})
autoUpdateTimeField := gen.FieldGORMTag("updated_at", func(tag field.GormTag) field.GormTag {
return tag.Append("autoUpdateTime")
})
autoCreateTimeField := gen.FieldGORMTag("created_at", func(tag field.GormTag) field.GormTag {
return tag.Append("autoCreateTime")
})
softDeleteField := gen.FieldType("delete_at", "gorm.DeletedAt")
versionField := gen.FieldType("version", "optimisticlock.Version")
// 模型自定义选项组
fieldOpts := []gen.ModelOpt{jsonField, idField, autoUpdateTimeField, autoCreateTimeField, softDeleteField, versionField}
// 创建全部模型文件, 并覆盖前面创建的同名模型
model := g.GenerateModel("sca_storage_face", fieldOpts...)
g.ApplyBasic(model)
g.Execute()
}

View File

@@ -0,0 +1,31 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package model
import (
"time"
"gorm.io/gorm"
)
const TableNameScaStorageFace = "sca_storage_face"
// ScaStorageFace 人脸特征向量表
type ScaStorageFace struct {
ID int64 `gorm:"column:id;type:bigint(20);primaryKey;autoIncrement:true;comment:主键;primary_key" json:"id,string"` // 主键
UserID string `gorm:"column:user_id;type:varchar(50);comment:用户ID" json:"user_id"` // 用户ID
FaceName string `gorm:"column:face_name;type:varchar(255);comment:人脸名称" json:"face_name"` // 人脸名称
FaceVector string `gorm:"column:face_vector;type:json;comment:人脸特征向量" json:"face_vector"` // 人脸特征向量
FaceImagePath string `gorm:"column:face_image_path;type:varchar(255);comment:人脸图像路径" json:"face_image_path"` // 人脸图像路径
FaceType string `gorm:"column:face_type;type:varchar(50);comment:人脸类型标识" json:"face_type"` // 人脸类型标识
CreatedAt time.Time `gorm:"column:created_at;type:timestamp;autoCreateTime;comment:创建时间" json:"created_at"` // 创建时间
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp;autoUpdateTime;comment:更新时间" json:"updated_at"` // 更新时间
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp;comment:删除时间" json:"deleted_at"` // 删除时间
}
// TableName ScaStorageFace's table name
func (*ScaStorageFace) TableName() string {
return TableNameScaStorageFace
}

View File

@@ -0,0 +1,76 @@
package mysql
import (
"log"
"os"
"schisandra-album-cloud-microservices/app/aisvc/model/mysql/model"
"schisandra-album-cloud-microservices/app/aisvc/model/mysql/query"
"time"
"github.com/asjdf/gorm-cache/cache"
"github.com/asjdf/gorm-cache/config"
"github.com/asjdf/gorm-cache/storage"
"github.com/redis/go-redis/v9"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
func NewMySQL(url string, maxOpenConn int, maxIdleConn int, client *redis.Client) (*gorm.DB, *query.Query) {
db, err := gorm.Open(mysql.Open(url), &gorm.Config{
SkipDefaultTransaction: true,
PrepareStmt: true,
Logger: logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags),
logger.Config{
SlowThreshold: time.Second, // 慢sql日志
LogLevel: logger.Error, // 级别
Colorful: true, // 颜色
IgnoreRecordNotFoundError: true, // 忽略RecordNotFoundError
ParameterizedQueries: true, // 格式化SQL语句
}),
})
if err != nil {
panic(err)
}
sqlDB, err := db.DB()
if err != nil {
panic(err)
}
sqlDB.SetMaxOpenConns(maxOpenConn)
sqlDB.SetMaxIdleConns(maxIdleConn)
useDB := query.Use(db)
// migrate
Migrate(db)
// cache
gormCache, err := cache.NewGorm2Cache(&config.CacheConfig{
CacheLevel: config.CacheLevelAll,
CacheStorage: storage.NewRedis(&storage.RedisStoreConfig{
KeyPrefix: "cache",
Client: client,
}),
InvalidateWhenUpdate: true, // when you create/update/delete objects, invalidate cache
CacheTTL: 10000, // 5000 ms
CacheMaxItemCnt: 0, // if length of objects retrieved one single time
AsyncWrite: true, // async write to cache
DebugMode: false,
DisableCachePenetrationProtect: true, // disable cache penetration protect
})
if err != nil {
panic(err)
}
err = db.Use(gormCache)
if err != nil {
panic(err)
}
return db, useDB
}
func Migrate(db *gorm.DB) {
err := db.AutoMigrate(
&model.ScaStorageFace{})
if err != nil {
panic(err)
}
}

View File

@@ -0,0 +1,103 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package query
import (
"context"
"database/sql"
"gorm.io/gorm"
"gorm.io/gen"
"gorm.io/plugin/dbresolver"
)
var (
Q = new(Query)
ScaStorageFace *scaStorageFace
)
func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
*Q = *Use(db, opts...)
ScaStorageFace = &Q.ScaStorageFace
}
func Use(db *gorm.DB, opts ...gen.DOOption) *Query {
return &Query{
db: db,
ScaStorageFace: newScaStorageFace(db, opts...),
}
}
type Query struct {
db *gorm.DB
ScaStorageFace scaStorageFace
}
func (q *Query) Available() bool { return q.db != nil }
func (q *Query) clone(db *gorm.DB) *Query {
return &Query{
db: db,
ScaStorageFace: q.ScaStorageFace.clone(db),
}
}
func (q *Query) ReadDB() *Query {
return q.ReplaceDB(q.db.Clauses(dbresolver.Read))
}
func (q *Query) WriteDB() *Query {
return q.ReplaceDB(q.db.Clauses(dbresolver.Write))
}
func (q *Query) ReplaceDB(db *gorm.DB) *Query {
return &Query{
db: db,
ScaStorageFace: q.ScaStorageFace.replaceDB(db),
}
}
type queryCtx struct {
ScaStorageFace IScaStorageFaceDo
}
func (q *Query) WithContext(ctx context.Context) *queryCtx {
return &queryCtx{
ScaStorageFace: q.ScaStorageFace.WithContext(ctx),
}
}
func (q *Query) Transaction(fc func(tx *Query) error, opts ...*sql.TxOptions) error {
return q.db.Transaction(func(tx *gorm.DB) error { return fc(q.clone(tx)) }, opts...)
}
func (q *Query) Begin(opts ...*sql.TxOptions) *QueryTx {
tx := q.db.Begin(opts...)
return &QueryTx{Query: q.clone(tx), Error: tx.Error}
}
type QueryTx struct {
*Query
Error error
}
func (q *QueryTx) Commit() error {
return q.db.Commit().Error
}
func (q *QueryTx) Rollback() error {
return q.db.Rollback().Error
}
func (q *QueryTx) SavePoint(name string) error {
return q.db.SavePoint(name).Error
}
func (q *QueryTx) RollbackTo(name string) error {
return q.db.RollbackTo(name).Error
}

View File

@@ -0,0 +1,413 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package query
import (
"context"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"gorm.io/gorm/schema"
"gorm.io/gen"
"gorm.io/gen/field"
"gorm.io/plugin/dbresolver"
"schisandra-album-cloud-microservices/app/aisvc/model/mysql/model"
)
func newScaStorageFace(db *gorm.DB, opts ...gen.DOOption) scaStorageFace {
_scaStorageFace := scaStorageFace{}
_scaStorageFace.scaStorageFaceDo.UseDB(db, opts...)
_scaStorageFace.scaStorageFaceDo.UseModel(&model.ScaStorageFace{})
tableName := _scaStorageFace.scaStorageFaceDo.TableName()
_scaStorageFace.ALL = field.NewAsterisk(tableName)
_scaStorageFace.ID = field.NewInt64(tableName, "id")
_scaStorageFace.UserID = field.NewString(tableName, "user_id")
_scaStorageFace.FaceName = field.NewString(tableName, "face_name")
_scaStorageFace.FaceVector = field.NewString(tableName, "face_vector")
_scaStorageFace.FaceImagePath = field.NewString(tableName, "face_image_path")
_scaStorageFace.FaceType = field.NewString(tableName, "face_type")
_scaStorageFace.CreatedAt = field.NewTime(tableName, "created_at")
_scaStorageFace.UpdatedAt = field.NewTime(tableName, "updated_at")
_scaStorageFace.DeletedAt = field.NewField(tableName, "deleted_at")
_scaStorageFace.fillFieldMap()
return _scaStorageFace
}
// scaStorageFace 人脸特征向量表
type scaStorageFace struct {
scaStorageFaceDo
ALL field.Asterisk
ID field.Int64 // 主键
UserID field.String // 用户ID
FaceName field.String // 人脸名称
FaceVector field.String // 人脸特征向量
FaceImagePath field.String // 人脸图像路径
FaceType field.String // 人脸类型标识
CreatedAt field.Time // 创建时间
UpdatedAt field.Time // 更新时间
DeletedAt field.Field // 删除时间
fieldMap map[string]field.Expr
}
func (s scaStorageFace) Table(newTableName string) *scaStorageFace {
s.scaStorageFaceDo.UseTable(newTableName)
return s.updateTableName(newTableName)
}
func (s scaStorageFace) As(alias string) *scaStorageFace {
s.scaStorageFaceDo.DO = *(s.scaStorageFaceDo.As(alias).(*gen.DO))
return s.updateTableName(alias)
}
func (s *scaStorageFace) updateTableName(table string) *scaStorageFace {
s.ALL = field.NewAsterisk(table)
s.ID = field.NewInt64(table, "id")
s.UserID = field.NewString(table, "user_id")
s.FaceName = field.NewString(table, "face_name")
s.FaceVector = field.NewString(table, "face_vector")
s.FaceImagePath = field.NewString(table, "face_image_path")
s.FaceType = field.NewString(table, "face_type")
s.CreatedAt = field.NewTime(table, "created_at")
s.UpdatedAt = field.NewTime(table, "updated_at")
s.DeletedAt = field.NewField(table, "deleted_at")
s.fillFieldMap()
return s
}
func (s *scaStorageFace) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
_f, ok := s.fieldMap[fieldName]
if !ok || _f == nil {
return nil, false
}
_oe, ok := _f.(field.OrderExpr)
return _oe, ok
}
func (s *scaStorageFace) fillFieldMap() {
s.fieldMap = make(map[string]field.Expr, 9)
s.fieldMap["id"] = s.ID
s.fieldMap["user_id"] = s.UserID
s.fieldMap["face_name"] = s.FaceName
s.fieldMap["face_vector"] = s.FaceVector
s.fieldMap["face_image_path"] = s.FaceImagePath
s.fieldMap["face_type"] = s.FaceType
s.fieldMap["created_at"] = s.CreatedAt
s.fieldMap["updated_at"] = s.UpdatedAt
s.fieldMap["deleted_at"] = s.DeletedAt
}
func (s scaStorageFace) clone(db *gorm.DB) scaStorageFace {
s.scaStorageFaceDo.ReplaceConnPool(db.Statement.ConnPool)
return s
}
func (s scaStorageFace) replaceDB(db *gorm.DB) scaStorageFace {
s.scaStorageFaceDo.ReplaceDB(db)
return s
}
type scaStorageFaceDo struct{ gen.DO }
type IScaStorageFaceDo interface {
gen.SubQuery
Debug() IScaStorageFaceDo
WithContext(ctx context.Context) IScaStorageFaceDo
WithResult(fc func(tx gen.Dao)) gen.ResultInfo
ReplaceDB(db *gorm.DB)
ReadDB() IScaStorageFaceDo
WriteDB() IScaStorageFaceDo
As(alias string) gen.Dao
Session(config *gorm.Session) IScaStorageFaceDo
Columns(cols ...field.Expr) gen.Columns
Clauses(conds ...clause.Expression) IScaStorageFaceDo
Not(conds ...gen.Condition) IScaStorageFaceDo
Or(conds ...gen.Condition) IScaStorageFaceDo
Select(conds ...field.Expr) IScaStorageFaceDo
Where(conds ...gen.Condition) IScaStorageFaceDo
Order(conds ...field.Expr) IScaStorageFaceDo
Distinct(cols ...field.Expr) IScaStorageFaceDo
Omit(cols ...field.Expr) IScaStorageFaceDo
Join(table schema.Tabler, on ...field.Expr) IScaStorageFaceDo
LeftJoin(table schema.Tabler, on ...field.Expr) IScaStorageFaceDo
RightJoin(table schema.Tabler, on ...field.Expr) IScaStorageFaceDo
Group(cols ...field.Expr) IScaStorageFaceDo
Having(conds ...gen.Condition) IScaStorageFaceDo
Limit(limit int) IScaStorageFaceDo
Offset(offset int) IScaStorageFaceDo
Count() (count int64, err error)
Scopes(funcs ...func(gen.Dao) gen.Dao) IScaStorageFaceDo
Unscoped() IScaStorageFaceDo
Create(values ...*model.ScaStorageFace) error
CreateInBatches(values []*model.ScaStorageFace, batchSize int) error
Save(values ...*model.ScaStorageFace) error
First() (*model.ScaStorageFace, error)
Take() (*model.ScaStorageFace, error)
Last() (*model.ScaStorageFace, error)
Find() ([]*model.ScaStorageFace, error)
FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.ScaStorageFace, err error)
FindInBatches(result *[]*model.ScaStorageFace, batchSize int, fc func(tx gen.Dao, batch int) error) error
Pluck(column field.Expr, dest interface{}) error
Delete(...*model.ScaStorageFace) (info gen.ResultInfo, err error)
Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error)
UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)
Updates(value interface{}) (info gen.ResultInfo, err error)
UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error)
UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)
UpdateColumns(value interface{}) (info gen.ResultInfo, err error)
UpdateFrom(q gen.SubQuery) gen.Dao
Attrs(attrs ...field.AssignExpr) IScaStorageFaceDo
Assign(attrs ...field.AssignExpr) IScaStorageFaceDo
Joins(fields ...field.RelationField) IScaStorageFaceDo
Preload(fields ...field.RelationField) IScaStorageFaceDo
FirstOrInit() (*model.ScaStorageFace, error)
FirstOrCreate() (*model.ScaStorageFace, error)
FindByPage(offset int, limit int) (result []*model.ScaStorageFace, count int64, err error)
ScanByPage(result interface{}, offset int, limit int) (count int64, err error)
Scan(result interface{}) (err error)
Returning(value interface{}, columns ...string) IScaStorageFaceDo
UnderlyingDB() *gorm.DB
schema.Tabler
}
func (s scaStorageFaceDo) Debug() IScaStorageFaceDo {
return s.withDO(s.DO.Debug())
}
func (s scaStorageFaceDo) WithContext(ctx context.Context) IScaStorageFaceDo {
return s.withDO(s.DO.WithContext(ctx))
}
func (s scaStorageFaceDo) ReadDB() IScaStorageFaceDo {
return s.Clauses(dbresolver.Read)
}
func (s scaStorageFaceDo) WriteDB() IScaStorageFaceDo {
return s.Clauses(dbresolver.Write)
}
func (s scaStorageFaceDo) Session(config *gorm.Session) IScaStorageFaceDo {
return s.withDO(s.DO.Session(config))
}
func (s scaStorageFaceDo) Clauses(conds ...clause.Expression) IScaStorageFaceDo {
return s.withDO(s.DO.Clauses(conds...))
}
func (s scaStorageFaceDo) Returning(value interface{}, columns ...string) IScaStorageFaceDo {
return s.withDO(s.DO.Returning(value, columns...))
}
func (s scaStorageFaceDo) Not(conds ...gen.Condition) IScaStorageFaceDo {
return s.withDO(s.DO.Not(conds...))
}
func (s scaStorageFaceDo) Or(conds ...gen.Condition) IScaStorageFaceDo {
return s.withDO(s.DO.Or(conds...))
}
func (s scaStorageFaceDo) Select(conds ...field.Expr) IScaStorageFaceDo {
return s.withDO(s.DO.Select(conds...))
}
func (s scaStorageFaceDo) Where(conds ...gen.Condition) IScaStorageFaceDo {
return s.withDO(s.DO.Where(conds...))
}
func (s scaStorageFaceDo) Order(conds ...field.Expr) IScaStorageFaceDo {
return s.withDO(s.DO.Order(conds...))
}
func (s scaStorageFaceDo) Distinct(cols ...field.Expr) IScaStorageFaceDo {
return s.withDO(s.DO.Distinct(cols...))
}
func (s scaStorageFaceDo) Omit(cols ...field.Expr) IScaStorageFaceDo {
return s.withDO(s.DO.Omit(cols...))
}
func (s scaStorageFaceDo) Join(table schema.Tabler, on ...field.Expr) IScaStorageFaceDo {
return s.withDO(s.DO.Join(table, on...))
}
func (s scaStorageFaceDo) LeftJoin(table schema.Tabler, on ...field.Expr) IScaStorageFaceDo {
return s.withDO(s.DO.LeftJoin(table, on...))
}
func (s scaStorageFaceDo) RightJoin(table schema.Tabler, on ...field.Expr) IScaStorageFaceDo {
return s.withDO(s.DO.RightJoin(table, on...))
}
func (s scaStorageFaceDo) Group(cols ...field.Expr) IScaStorageFaceDo {
return s.withDO(s.DO.Group(cols...))
}
func (s scaStorageFaceDo) Having(conds ...gen.Condition) IScaStorageFaceDo {
return s.withDO(s.DO.Having(conds...))
}
func (s scaStorageFaceDo) Limit(limit int) IScaStorageFaceDo {
return s.withDO(s.DO.Limit(limit))
}
func (s scaStorageFaceDo) Offset(offset int) IScaStorageFaceDo {
return s.withDO(s.DO.Offset(offset))
}
func (s scaStorageFaceDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IScaStorageFaceDo {
return s.withDO(s.DO.Scopes(funcs...))
}
func (s scaStorageFaceDo) Unscoped() IScaStorageFaceDo {
return s.withDO(s.DO.Unscoped())
}
func (s scaStorageFaceDo) Create(values ...*model.ScaStorageFace) error {
if len(values) == 0 {
return nil
}
return s.DO.Create(values)
}
func (s scaStorageFaceDo) CreateInBatches(values []*model.ScaStorageFace, batchSize int) error {
return s.DO.CreateInBatches(values, batchSize)
}
// Save : !!! underlying implementation is different with GORM
// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values)
func (s scaStorageFaceDo) Save(values ...*model.ScaStorageFace) error {
if len(values) == 0 {
return nil
}
return s.DO.Save(values)
}
func (s scaStorageFaceDo) First() (*model.ScaStorageFace, error) {
if result, err := s.DO.First(); err != nil {
return nil, err
} else {
return result.(*model.ScaStorageFace), nil
}
}
func (s scaStorageFaceDo) Take() (*model.ScaStorageFace, error) {
if result, err := s.DO.Take(); err != nil {
return nil, err
} else {
return result.(*model.ScaStorageFace), nil
}
}
func (s scaStorageFaceDo) Last() (*model.ScaStorageFace, error) {
if result, err := s.DO.Last(); err != nil {
return nil, err
} else {
return result.(*model.ScaStorageFace), nil
}
}
func (s scaStorageFaceDo) Find() ([]*model.ScaStorageFace, error) {
result, err := s.DO.Find()
return result.([]*model.ScaStorageFace), err
}
func (s scaStorageFaceDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.ScaStorageFace, err error) {
buf := make([]*model.ScaStorageFace, 0, batchSize)
err = s.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error {
defer func() { results = append(results, buf...) }()
return fc(tx, batch)
})
return results, err
}
func (s scaStorageFaceDo) FindInBatches(result *[]*model.ScaStorageFace, batchSize int, fc func(tx gen.Dao, batch int) error) error {
return s.DO.FindInBatches(result, batchSize, fc)
}
func (s scaStorageFaceDo) Attrs(attrs ...field.AssignExpr) IScaStorageFaceDo {
return s.withDO(s.DO.Attrs(attrs...))
}
func (s scaStorageFaceDo) Assign(attrs ...field.AssignExpr) IScaStorageFaceDo {
return s.withDO(s.DO.Assign(attrs...))
}
func (s scaStorageFaceDo) Joins(fields ...field.RelationField) IScaStorageFaceDo {
for _, _f := range fields {
s = *s.withDO(s.DO.Joins(_f))
}
return &s
}
func (s scaStorageFaceDo) Preload(fields ...field.RelationField) IScaStorageFaceDo {
for _, _f := range fields {
s = *s.withDO(s.DO.Preload(_f))
}
return &s
}
func (s scaStorageFaceDo) FirstOrInit() (*model.ScaStorageFace, error) {
if result, err := s.DO.FirstOrInit(); err != nil {
return nil, err
} else {
return result.(*model.ScaStorageFace), nil
}
}
func (s scaStorageFaceDo) FirstOrCreate() (*model.ScaStorageFace, error) {
if result, err := s.DO.FirstOrCreate(); err != nil {
return nil, err
} else {
return result.(*model.ScaStorageFace), nil
}
}
func (s scaStorageFaceDo) FindByPage(offset int, limit int) (result []*model.ScaStorageFace, count int64, err error) {
result, err = s.Offset(offset).Limit(limit).Find()
if err != nil {
return
}
if size := len(result); 0 < limit && 0 < size && size < limit {
count = int64(size + offset)
return
}
count, err = s.Offset(-1).Limit(-1).Count()
return
}
func (s scaStorageFaceDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) {
count, err = s.Count()
if err != nil {
return
}
err = s.Offset(offset).Limit(limit).Scan(result)
return
}
func (s scaStorageFaceDo) Scan(result interface{}) (err error) {
return s.DO.Scan(result)
}
func (s scaStorageFaceDo) Delete(models ...*model.ScaStorageFace) (result gen.ResultInfo, err error) {
return s.DO.Delete(models)
}
func (s *scaStorageFaceDo) withDO(do gen.Dao) *scaStorageFaceDo {
s.DO = *do.(*gen.DO)
return s
}

39
app/aisvc/rpc/aisvc.go Normal file
View File

@@ -0,0 +1,39 @@
package main
import (
"flag"
"fmt"
"schisandra-album-cloud-microservices/app/aisvc/rpc/internal/config"
aiserviceServer "schisandra-album-cloud-microservices/app/aisvc/rpc/internal/server/aiservice"
"schisandra-album-cloud-microservices/app/aisvc/rpc/internal/svc"
"schisandra-album-cloud-microservices/app/aisvc/rpc/pb"
"github.com/zeromicro/go-zero/core/conf"
"github.com/zeromicro/go-zero/core/service"
"github.com/zeromicro/go-zero/zrpc"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)
var configFile = flag.String("f", "rpc/etc/aisvc.yaml", "the config file")
func main() {
flag.Parse()
var c config.Config
conf.MustLoad(*configFile, &c)
ctx := svc.NewServiceContext(c)
s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
pb.RegisterAiServiceServer(grpcServer, aiserviceServer.NewAiServiceServer(ctx))
if c.Mode == service.DevMode || c.Mode == service.TestMode {
reflection.Register(grpcServer)
}
})
defer s.Stop()
fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)
s.Start()
}

18
app/aisvc/rpc/aisvc.proto Normal file
View File

@@ -0,0 +1,18 @@
syntax = "proto3";
package ai;
option go_package = "./pb";
message FaceRecognitionRequest {
bytes face = 1;
string user_id = 2;
}
message FaceRecognitionResponse {
int64 face_id = 1;
}
service AiService {
// FaceRecognition
rpc FaceRecognition (FaceRecognitionRequest) returns (FaceRecognitionResponse);
}

View File

@@ -0,0 +1,40 @@
// Code generated by goctl. DO NOT EDIT.
// goctl 1.7.3
// Source: aisvc.proto
package aiservice
import (
"context"
"schisandra-album-cloud-microservices/app/aisvc/rpc/pb"
"github.com/zeromicro/go-zero/zrpc"
"google.golang.org/grpc"
)
type (
FaceRecognitionRequest = pb.FaceRecognitionRequest
FaceRecognitionResponse = pb.FaceRecognitionResponse
AiService interface {
// FaceRecognition
FaceRecognition(ctx context.Context, in *FaceRecognitionRequest, opts ...grpc.CallOption) (*FaceRecognitionResponse, error)
}
defaultAiService struct {
cli zrpc.Client
}
)
func NewAiService(cli zrpc.Client) AiService {
return &defaultAiService{
cli: cli,
}
}
// FaceRecognition
func (m *defaultAiService) FaceRecognition(ctx context.Context, in *FaceRecognitionRequest, opts ...grpc.CallOption) (*FaceRecognitionResponse, error) {
client := pb.NewAiServiceClient(m.cli.Conn())
return client.FaceRecognition(ctx, in, opts...)
}

View File

@@ -0,0 +1,22 @@
Name: aisvc.rpc
ListenOn: 0.0.0.0:8080
Etcd:
Hosts:
- 127.0.0.1:2379
Key: aisvc.rpc
# MySQL 配置
Mysql:
# 数据源dsn
DataSource: root:LDQ20020618xxx@tcp(1.95.0.111:3306)/schisandra-cloud-album?charset=utf8mb4&parseTime=True&loc=Local
# 最大连接数
MaxOpenConn: 10
# 最大空闲连接数
MaxIdleConn: 5
# RedisConf 配置
RedisConf:
# Redis 地址
Host: 1.95.0.111:6379
# Redis 密码
Pass: LDQ20020618xxx
# Redis 数据库
DB: 0

View File

@@ -0,0 +1,3 @@
package main
//go:generate goctl rpc protoc aisvc.proto --go_out=. --go-grpc_out=. --zrpc_out=. --client=true -m --style=go_zero

View File

@@ -0,0 +1,18 @@
package config
import "github.com/zeromicro/go-zero/zrpc"
type Config struct {
zrpc.RpcServerConf
Mysql struct {
DataSource string
MaxOpenConn int
MaxIdleConn int
}
RedisConf struct {
Host string
Pass string
DB int
}
}

View File

@@ -0,0 +1,355 @@
package aiservicelogic
import (
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/Kagami/go-face"
"github.com/ccpwcn/kgo"
"github.com/zeromicro/go-zero/core/logx"
"image"
"image/jpeg"
"os"
"path/filepath"
"schisandra-album-cloud-microservices/app/aisvc/model/mysql/model"
"schisandra-album-cloud-microservices/app/aisvc/rpc/internal/svc"
"schisandra-album-cloud-microservices/app/aisvc/rpc/pb"
"schisandra-album-cloud-microservices/common/constant"
"strconv"
"sync"
"time"
)
type FaceRecognitionLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
directoryCache sync.Map
wg sync.WaitGroup
mu sync.Mutex
}
func NewFaceRecognitionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FaceRecognitionLogic {
return &FaceRecognitionLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
directoryCache: sync.Map{},
wg: sync.WaitGroup{},
mu: sync.Mutex{},
}
}
// FaceRecognition 人脸识别
func (l *FaceRecognitionLogic) FaceRecognition(in *pb.FaceRecognitionRequest) (*pb.FaceRecognitionResponse, error) {
// 提取人脸特征
faceFeatures, err := l.svcCtx.FaceRecognizer.RecognizeSingle(in.GetFace())
if err != nil {
return nil, err
}
if faceFeatures == nil {
return nil, nil
}
hashKey := fmt.Sprintf("user:%s:faces", in.GetUserId())
// 从 Redis 加载人脸数据
samples, ids, err := l.loadFacesFromRedisHash(hashKey)
if err != nil {
return nil, fmt.Errorf("failed to query Redis: %v", err)
}
// 如果缓存中没有数据,则查询数据库
if len(samples) == 0 {
samples, ids, err = l.loadExistingFaces(in.GetUserId())
if err != nil {
return nil, err
}
// 如果数据库也没有数据,直接保存当前人脸
if len(samples) == 0 || len(ids) == 0 {
return l.saveNewFace(in, faceFeatures, hashKey)
}
// 将数据写入 Redis
err = l.cacheFacesToRedisHash(hashKey, samples, ids)
if err != nil {
return nil, fmt.Errorf("failed to cache faces to Redis: %v", err)
}
}
// 设置人脸特征
l.svcCtx.FaceRecognizer.SetSamples(samples, ids)
// 人脸分类
classify := l.svcCtx.FaceRecognizer.ClassifyThreshold(faceFeatures.Descriptor, 0.6)
if classify >= 0 {
return &pb.FaceRecognitionResponse{
FaceId: int64(ids[classify]),
}, nil
}
// 如果未找到匹配的人脸,则保存为新样本
return l.saveNewFace(in, faceFeatures, hashKey)
}
// 保存新的人脸样本到数据库和 Redis
func (l *FaceRecognitionLogic) saveNewFace(in *pb.FaceRecognitionRequest, faceFeatures *face.Face, hashKey string) (*pb.FaceRecognitionResponse, error) {
// 人脸有效性判断 (大小必须大于50)
if !l.isFaceValid(faceFeatures.Rectangle) {
return nil, nil
}
// 保存人脸图片到本地
faceImagePath, err := l.saveCroppedFaceToLocal(in.GetFace(), faceFeatures.Rectangle, "face_samples", in.GetUserId())
if err != nil {
return nil, err
}
// 保存到数据库
storageFace, err := l.saveFaceToDatabase(in.GetUserId(), faceFeatures.Descriptor, faceImagePath)
if err != nil {
return nil, err
}
// 将新增数据写入 Redis
err = l.appendFaceToRedisHash(hashKey, storageFace.ID, faceFeatures.Descriptor)
if err != nil {
return nil, fmt.Errorf("failed to append face to Redis: %v", err)
}
return &pb.FaceRecognitionResponse{
FaceId: storageFace.ID,
}, nil
}
// 加载数据库中的已有人脸
func (l *FaceRecognitionLogic) loadExistingFaces(userId string) ([]face.Descriptor, []int32, error) {
if userId == "" {
return nil, nil, fmt.Errorf("user ID is required")
}
storageFace := l.svcCtx.DB.ScaStorageFace
existingFaces, err := storageFace.
Select(storageFace.FaceVector, storageFace.ID).
Where(storageFace.UserID.Eq(userId), storageFace.FaceType.Eq(constant.FaceTypeSample)).
Find()
if err != nil {
return nil, nil, err
}
if len(existingFaces) == 0 {
return nil, nil, nil
}
var samples []face.Descriptor
var ids []int32
// 使用并发处理每个数据
for _, existingFace := range existingFaces {
l.wg.Add(1)
go func(faceData *model.ScaStorageFace) {
defer l.wg.Done()
var descriptor face.Descriptor
if err = json.Unmarshal([]byte(faceData.FaceVector), &descriptor); err != nil {
l.Errorf("failed to unmarshal face vector: %v", err)
return
}
// 使用锁来保证并发访问时对切片的安全操作
l.mu.Lock()
samples = append(samples, descriptor)
ids = append(ids, int32(faceData.ID))
l.mu.Unlock()
}(existingFace)
}
l.wg.Wait()
return samples, ids, nil
}
const (
minFaceWidth = 50 // 最小允许的人脸宽度
minFaceHeight = 50 // 最小允许的人脸高度
)
// 判断人脸是否有效
func (l *FaceRecognitionLogic) isFaceValid(rect image.Rectangle) bool {
width := rect.Dx()
height := rect.Dy()
return width >= minFaceWidth && height >= minFaceHeight
}
// 保存人脸特征和路径到数据库
func (l *FaceRecognitionLogic) saveFaceToDatabase(userId string, descriptor face.Descriptor, faceImagePath string) (*model.ScaStorageFace, error) {
jsonBytes, err := json.Marshal(descriptor)
if err != nil {
return nil, err
}
storageFace := model.ScaStorageFace{
FaceVector: string(jsonBytes),
FaceImagePath: faceImagePath,
FaceType: constant.FaceTypeSample,
UserID: userId,
}
err = l.svcCtx.DB.ScaStorageFace.Create(&storageFace)
if err != nil {
return nil, err
}
return &storageFace, nil
}
func (l *FaceRecognitionLogic) saveCroppedFaceToLocal(faceImage []byte, rect image.Rectangle, baseSavePath string, userID string) (string, error) {
// 动态生成用户目录和时间分级目录
subDir := filepath.Join(baseSavePath, userID, time.Now().Format("2006/01")) // 格式:<baseSavePath>/<userID>/YYYY/MM
// 缓存目录检查,避免重复调用 os.MkdirAll
if !l.isDirectoryCached(subDir) {
if err := os.MkdirAll(subDir, os.ModePerm); err != nil {
return "", fmt.Errorf("failed to create directory: %w", err)
}
l.cacheDirectory(subDir) // 缓存已创建的目录路径
}
// 解码图像
img, _, err := image.Decode(bytes.NewReader(faceImage))
if err != nil {
return "", fmt.Errorf("image decode failed: %w", err)
}
// 获取图像边界
imgBounds := img.Bounds()
// 增加边距(比如 20 像素)
margin := 20
extendedRect := image.Rect(
max(rect.Min.X-margin, imgBounds.Min.X), // 确保不超出左边界
max(rect.Min.Y-margin, imgBounds.Min.Y), // 确保不超出上边界
min(rect.Max.X+margin, imgBounds.Max.X), // 确保不超出右边界
min(rect.Max.Y+margin, imgBounds.Max.Y), // 确保不超出下边界
)
// 裁剪图像
croppedImage := img.(interface {
SubImage(r image.Rectangle) image.Image
}).SubImage(extendedRect)
// 生成唯一文件名(时间戳 + UUID
fileName := fmt.Sprintf("%s_%s.jpg", time.Now().Format("20060102_150405"), kgo.SimpleUuid())
outputPath := filepath.Join(subDir, fileName)
// 写入文件
if err = l.writeImageToFile(outputPath, croppedImage); err != nil {
return "", err
}
return outputPath, nil
}
// 判断目录是否已缓存
func (l *FaceRecognitionLogic) isDirectoryCached(dir string) bool {
_, exists := l.directoryCache.Load(dir)
return exists
}
// 缓存目录
func (l *FaceRecognitionLogic) cacheDirectory(dir string) {
l.directoryCache.Store(dir, struct{}{})
}
// 将图像写入文件
func (l *FaceRecognitionLogic) writeImageToFile(path string, img image.Image) error {
file, err := os.Create(path)
if err != nil {
return fmt.Errorf("failed to create file: %w", err)
}
defer func(file *os.File) {
_ = file.Close()
}(file)
if err = jpeg.Encode(file, img, nil); err != nil {
return fmt.Errorf("failed to encode and save image: %w", err)
}
return nil
}
// 从 Redis 的 Hash 中加载人脸数据
func (l *FaceRecognitionLogic) loadFacesFromRedisHash(hashKey string) ([]face.Descriptor, []int32, error) {
// 从 Redis 获取 Hash 的所有字段和值
data, err := l.svcCtx.RedisClient.HGetAll(l.ctx, hashKey).Result()
if err != nil {
return nil, nil, err
}
var samples []face.Descriptor
var ids []int32
for idStr, descriptorStr := range data {
var descriptor face.Descriptor
if err = json.Unmarshal([]byte(descriptorStr), &descriptor); err != nil {
return nil, nil, err
}
// 转换 ID 为 int32
id, err := parseInt32(idStr)
if err != nil {
return nil, nil, err
}
samples = append(samples, descriptor)
ids = append(ids, id)
}
return samples, ids, nil
}
// 将人脸数据写入 Redis 的 Hash
func (l *FaceRecognitionLogic) cacheFacesToRedisHash(hashKey string, samples []face.Descriptor, ids []int32) error {
// 开启事务
pipe := l.svcCtx.RedisClient.Pipeline()
for i := range samples {
descriptorData, err := json.Marshal(samples[i])
if err != nil {
return err
}
// 使用 HSET 设置 Hash 字段和值
pipe.HSet(l.ctx, hashKey, fmt.Sprintf("%d", ids[i]), descriptorData)
}
// 设置缓存过期时间
pipe.Expire(l.ctx, hashKey, 3600*time.Second)
_, err := pipe.Exec(l.ctx)
return err
}
// 将新增的人脸数据追加到 Redis 的 Hash
func (l *FaceRecognitionLogic) appendFaceToRedisHash(hashKey string, id int64, descriptor face.Descriptor) error {
descriptorData, err := json.Marshal(descriptor)
if err != nil {
return err
}
// 追加数据到 Hash
err = l.svcCtx.RedisClient.HSet(l.ctx, hashKey, fmt.Sprintf("%d", id), descriptorData).Err()
if err != nil {
return err
}
// 检查是否已设置过期时间
ttl, err := l.svcCtx.RedisClient.TTL(l.ctx, hashKey).Result()
if err != nil {
return err
}
// 如果未设置过期时间或已经过期,设置固定过期时间
if ttl < 0 {
err = l.svcCtx.RedisClient.Expire(l.ctx, hashKey, 3600*time.Second).Err()
if err != nil {
return err
}
}
return nil
}
// 辅助函数:字符串转换为 int32
func parseInt32(s string) (int32, error) {
var i int64
var err error
if i, err = strconv.ParseInt(s, 10, 32); err != nil {
return 0, err
}
return int32(i), nil
}

View File

@@ -0,0 +1,30 @@
// Code generated by goctl. DO NOT EDIT.
// goctl 1.7.3
// Source: aisvc.proto
package server
import (
"context"
"schisandra-album-cloud-microservices/app/aisvc/rpc/internal/logic/aiservice"
"schisandra-album-cloud-microservices/app/aisvc/rpc/internal/svc"
"schisandra-album-cloud-microservices/app/aisvc/rpc/pb"
)
type AiServiceServer struct {
svcCtx *svc.ServiceContext
pb.UnimplementedAiServiceServer
}
func NewAiServiceServer(svcCtx *svc.ServiceContext) *AiServiceServer {
return &AiServiceServer{
svcCtx: svcCtx,
}
}
// FaceRecognition
func (s *AiServiceServer) FaceRecognition(ctx context.Context, in *pb.FaceRecognitionRequest) (*pb.FaceRecognitionResponse, error) {
l := aiservicelogic.NewFaceRecognitionLogic(ctx, s.svcCtx)
return l.FaceRecognition(in)
}

View File

@@ -0,0 +1,29 @@
package svc
import (
"github.com/Kagami/go-face"
"github.com/redis/go-redis/v9"
"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/face_recognizer"
"schisandra-album-cloud-microservices/common/redisx"
)
type ServiceContext struct {
Config config.Config
FaceRecognizer *face.Recognizer
DB *query.Query
RedisClient *redis.Client
}
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)
return &ServiceContext{
Config: c,
FaceRecognizer: face_recognizer.NewFaceRecognition(),
DB: queryDB,
RedisClient: redisClient,
}
}

View File

@@ -0,0 +1,192 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.35.1
// protoc v3.19.4
// source: aisvc.proto
package pb
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type FaceRecognitionRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Face []byte `protobuf:"bytes,1,opt,name=face,proto3" json:"face,omitempty"`
UserId string `protobuf:"bytes,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
}
func (x *FaceRecognitionRequest) Reset() {
*x = FaceRecognitionRequest{}
mi := &file_aisvc_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *FaceRecognitionRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FaceRecognitionRequest) ProtoMessage() {}
func (x *FaceRecognitionRequest) ProtoReflect() protoreflect.Message {
mi := &file_aisvc_proto_msgTypes[0]
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 FaceRecognitionRequest.ProtoReflect.Descriptor instead.
func (*FaceRecognitionRequest) Descriptor() ([]byte, []int) {
return file_aisvc_proto_rawDescGZIP(), []int{0}
}
func (x *FaceRecognitionRequest) GetFace() []byte {
if x != nil {
return x.Face
}
return nil
}
func (x *FaceRecognitionRequest) GetUserId() string {
if x != nil {
return x.UserId
}
return ""
}
type FaceRecognitionResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
FaceId int64 `protobuf:"varint,1,opt,name=face_id,json=faceId,proto3" json:"face_id,omitempty"`
}
func (x *FaceRecognitionResponse) Reset() {
*x = FaceRecognitionResponse{}
mi := &file_aisvc_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *FaceRecognitionResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*FaceRecognitionResponse) ProtoMessage() {}
func (x *FaceRecognitionResponse) ProtoReflect() protoreflect.Message {
mi := &file_aisvc_proto_msgTypes[1]
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 FaceRecognitionResponse.ProtoReflect.Descriptor instead.
func (*FaceRecognitionResponse) Descriptor() ([]byte, []int) {
return file_aisvc_proto_rawDescGZIP(), []int{1}
}
func (x *FaceRecognitionResponse) GetFaceId() int64 {
if x != nil {
return x.FaceId
}
return 0
}
var File_aisvc_proto protoreflect.FileDescriptor
var file_aisvc_proto_rawDesc = []byte{
0x0a, 0x0b, 0x61, 0x69, 0x73, 0x76, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x02, 0x61,
0x69, 0x22, 0x45, 0x0a, 0x16, 0x46, 0x61, 0x63, 0x65, 0x52, 0x65, 0x63, 0x6f, 0x67, 0x6e, 0x69,
0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x66,
0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x66, 0x61, 0x63, 0x65, 0x12,
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,
}
var (
file_aisvc_proto_rawDescOnce sync.Once
file_aisvc_proto_rawDescData = file_aisvc_proto_rawDesc
)
func file_aisvc_proto_rawDescGZIP() []byte {
file_aisvc_proto_rawDescOnce.Do(func() {
file_aisvc_proto_rawDescData = protoimpl.X.CompressGZIP(file_aisvc_proto_rawDescData)
})
return file_aisvc_proto_rawDescData
}
var file_aisvc_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_aisvc_proto_goTypes = []any{
(*FaceRecognitionRequest)(nil), // 0: ai.FaceRecognitionRequest
(*FaceRecognitionResponse)(nil), // 1: ai.FaceRecognitionResponse
}
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
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
}
func init() { file_aisvc_proto_init() }
func file_aisvc_proto_init() {
if File_aisvc_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_aisvc_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_aisvc_proto_goTypes,
DependencyIndexes: file_aisvc_proto_depIdxs,
MessageInfos: file_aisvc_proto_msgTypes,
}.Build()
File_aisvc_proto = out.File
file_aisvc_proto_rawDesc = nil
file_aisvc_proto_goTypes = nil
file_aisvc_proto_depIdxs = nil
}

View File

@@ -0,0 +1,123 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc v3.19.4
// source: aisvc.proto
package pb
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
AiService_FaceRecognition_FullMethodName = "/ai.AiService/FaceRecognition"
)
// AiServiceClient is the client API for AiService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type AiServiceClient interface {
// FaceRecognition
FaceRecognition(ctx context.Context, in *FaceRecognitionRequest, opts ...grpc.CallOption) (*FaceRecognitionResponse, error)
}
type aiServiceClient struct {
cc grpc.ClientConnInterface
}
func NewAiServiceClient(cc grpc.ClientConnInterface) AiServiceClient {
return &aiServiceClient{cc}
}
func (c *aiServiceClient) FaceRecognition(ctx context.Context, in *FaceRecognitionRequest, opts ...grpc.CallOption) (*FaceRecognitionResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(FaceRecognitionResponse)
err := c.cc.Invoke(ctx, AiService_FaceRecognition_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)
mustEmbedUnimplementedAiServiceServer()
}
// UnimplementedAiServiceServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedAiServiceServer struct{}
func (UnimplementedAiServiceServer) FaceRecognition(context.Context, *FaceRecognitionRequest) (*FaceRecognitionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method FaceRecognition not implemented")
}
func (UnimplementedAiServiceServer) mustEmbedUnimplementedAiServiceServer() {}
func (UnimplementedAiServiceServer) testEmbeddedByValue() {}
// UnsafeAiServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to AiServiceServer will
// result in compilation errors.
type UnsafeAiServiceServer interface {
mustEmbedUnimplementedAiServiceServer()
}
func RegisterAiServiceServer(s grpc.ServiceRegistrar, srv AiServiceServer) {
// If the following call pancis, it indicates UnimplementedAiServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&AiService_ServiceDesc, srv)
}
func _AiService_FaceRecognition_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(FaceRecognitionRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AiServiceServer).FaceRecognition(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: AiService_FaceRecognition_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AiServiceServer).FaceRecognition(ctx, req.(*FaceRecognitionRequest))
}
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)
var AiService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "ai.AiService",
HandlerType: (*AiServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "FaceRecognition",
Handler: _AiService_FaceRecognition_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "aisvc.proto",
}