Zsh自动完成文件的一个例子

亚马逊发布了kiro ,基于Claude,于是下载安装文件,发现在它的安装文件里面有那么一个文件。 文件内容如下 #compdef kiro local arguments arguments=( '(-d --diff)'{-d,--diff}'[compare two files with each other]:file to compare:_files:file to compare with:_files' \*'--folder-uri[open a window with given folder uri(s)]:folder uri: ' \*{-a,--add}'[add folder(s) to the last active window]:directory:_directories' '(-g --goto)'{-g,--goto}'[open a file at the path on the specified line and column position]:file\:line[\:column]:_files -r \:' '(-n --new-window -r --reuse-window)'{-n,--new-window}'[force to open a new window]' '(-n --new-window -r --reuse-window)'{-r,--reuse-window}'[force to open a file or folder in an already opened window]' '(-w --wait)'{-w,--wait}'[wait for the files to be closed before returning]' '--locale=[the locale to use (e.g. en-US or zh-TW)]:locale (e.g. en-US or zh-TW):(de en en-US es fr it ja ko ru zh-CN zh-TW bg hu pt-br tr)' '--user-data-dir[specify the directory that user data is kept in]:directory:_directories' '(- *)'{-v,--version}'[print version]' '(- *)'{-h,--help}'[print usage]' '--telemetry[show all telemetry events which VS code collects]' '--extensions-dir[set the root path for extensions]:root path:_directories' '--list-extensions[list the installed extensions]' '--category[filters installed extension list by category, when using --list-extensions]' '--show-versions[show versions of installed extensions, when using --list-extensions]' '--install-extension[install an extension]:id or path:_files -g "*.vsix(-.)"' '--uninstall-extension[uninstall an extension]:id or path:_files -g "*.vsix(-.)"' '--update-extensions[update the installed extensions]' '--enable-proposed-api[enables proposed API features for extensions]::extension id: ' '--verbose[print verbose output (implies --wait)]' '--log[log level to use]:level [info]:(critical error warn info debug trace off)' '(-s --status)'{-s,--status}'[print process usage and diagnostics information]' '(-p --performance)'{-p,--performance}'[start with the "Developer: Startup Performance" command enabled]' '--prof-startup[run CPU profiler during startup]' '(--disable-extension --disable-extensions)--disable-extensions[disable all installed extensions]' \*'--disable-extension[disable an extension]:extension id: ' '--inspect-extensions[allow debugging and profiling of extensions]' '--inspect-brk-extensions[allow debugging and profiling of extensions with the extension host being paused after start]' '--disable-gpu[disable GPU hardware acceleration]' '*:file or directory:_files' ) _arguments -s -S $arguments 然后让claude 4.0来讲解下自动完成文件的编写实践。 ...

2025-07-16 · 3 分钟 · czyt

使用go编写一个简单的人脸识别服务

本文提供两种实现方案,接口设计完全一致(人员登记、多样本、单人/多人识别、统计等): 方案一:基于 go-face (dlib),需要编译 CGO 依赖,特征为 128 维,使用欧氏距离分类 方案二(推荐,效果更好):基于 go-insightface (SCRFD 检测 + ArcFace 特征提取),纯 Go + ONNX Runtime 动态加载,无需 CGO/OpenCV,特征为 512 维,使用余弦相似度 + Top-1/Top-2 margin 精确匹配,识别精度显著更高 方案一:基于 go-face(dlib) 需要下载相关的模型 https://github.com/Kagami/go-face-testdata 下面的models 代码如下 package main import ( "encoding/base64" "encoding/json" "errors" "fmt" "io" "log" "math" "net/http" "os" "path/filepath" "strconv" "sync" "time" "unsafe" "github.com/Kagami/go-face" "github.com/gorilla/mux" ) // Config 配置结构 type Config struct { Port string `json:"port"` ModelsDir string `json:"models_dir"` UploadsDir string `json:"uploads_dir"` TempDir string `json:"temp_dir"` DataFile string `json:"data_file"` MaxFileSize int64 `json:"max_file_size"` DefaultThreshold float32 `json:"default_threshold"` LogLevel string `json:"log_level"` } // Person 人员结构(支持多样本) type Person struct { ID int `json:"id"` Name string `json:"name"` Samples []FaceSample `json:"samples"` Created time.Time `json:"created"` Updated time.Time `json:"updated"` } // FaceSample 人脸样本 type FaceSample struct { ID int `json:"id"` PersonID int `json:"person_id"` Descriptor string `json:"descriptor"` // base64编码的特征向量 ImagePath string `json:"image_path"` ImageURL string `json:"image_url"` Quality float32 `json:"quality"` // 人脸质量评分 Created time.Time `json:"created"` } // RecognitionResult 识别结果 type RecognitionResult struct { PersonID int `json:"person_id"` PersonName string `json:"person_name"` Confidence float32 `json:"confidence"` Distance float32 `json:"distance"` SampleID int `json:"sample_id"` } // FaceDetection 人脸检测结果 type FaceDetection struct { Index int `json:"index"` Rectangle map[string]int `json:"rectangle"` Recognized bool `json:"recognized"` Result *RecognitionResult `json:"result,omitempty"` Message string `json:"message,omitempty"` } // Response 通用响应结构 type Response struct { Success bool `json:"success"` Message string `json:"message"` Data interface{} `json:"data,omitempty"` Timestamp time.Time `json:"timestamp"` } // Statistics 统计信息 type Statistics struct { TotalPersons int `json:"total_persons"` TotalSamples int `json:"total_samples"` RecognitionCount int `json:"recognition_count"` RegistrationCount int `json:"registration_count"` } // FaceService 人脸识别服务 type FaceService struct { config *Config recognizer *face.Recognizer persons map[int]*Person samples map[int]*FaceSample mu sync.RWMutex nextPersonID int nextSampleID int stats Statistics // 用于分类的数据 classifierSamples []face.Descriptor classifierCats []int32 classifierLabels []string } // NewFaceService 初始化人脸识别服务 func NewFaceService(config *Config) (*FaceService, error) { // 初始化人脸识别器 rec, err := face.NewRecognizer(config.ModelsDir) if err != nil { return nil, fmt.Errorf("无法初始化人脸识别器: %v", err) } // 创建必要的目录 dirs := []string{config.UploadsDir, config.TempDir} for _, dir := range dirs { if err := os.MkdirAll(dir, 0755); err != nil { return nil, fmt.Errorf("创建目录 %s 失败: %v", dir, err) } } fs := &FaceService{ config: config, recognizer: rec, persons: make(map[int]*Person), samples: make(map[int]*FaceSample), nextPersonID: 1, nextSampleID: 1, classifierSamples: make([]face.Descriptor, 0), classifierCats: make([]int32, 0), classifierLabels: make([]string, 0), } // 加载已保存的数据 if err := fs.loadData(); err != nil { log.Printf("加载数据失败: %v", err) } return fs, nil } // Close 关闭资源 func (fs *FaceService) Close() { fs.recognizer.Close() } // 数据持久化相关方法 func (fs *FaceService) saveData() error { fs.mu.RLock() defer fs.mu.RUnlock() data := struct { Persons map[int]*Person `json:"persons"` Samples map[int]*FaceSample `json:"samples"` NextPersonID int `json:"next_person_id"` NextSampleID int `json:"next_sample_id"` Stats Statistics `json:"stats"` }{ Persons: fs.persons, Samples: fs.samples, NextPersonID: fs.nextPersonID, NextSampleID: fs.nextSampleID, Stats: fs.stats, } file, err := os.Create(fs.config.DataFile) if err != nil { return err } defer file.Close() encoder := json.NewEncoder(file) encoder.SetIndent("", " ") return encoder.Encode(data) } func (fs *FaceService) loadData() error { if _, err := os.Stat(fs.config.DataFile); os.IsNotExist(err) { return nil // 文件不存在,使用默认值 } file, err := os.Open(fs.config.DataFile) if err != nil { return err } defer file.Close() var data struct { Persons map[int]*Person `json:"persons"` Samples map[int]*FaceSample `json:"samples"` NextPersonID int `json:"next_person_id"` NextSampleID int `json:"next_sample_id"` Stats Statistics `json:"stats"` } if err := json.NewDecoder(file).Decode(&data); err != nil { return err } fs.mu.Lock() fs.persons = data.Persons fs.samples = data.Samples fs.nextPersonID = data.NextPersonID fs.nextSampleID = data.NextSampleID fs.stats = data.Stats fs.mu.Unlock() // 重建分类器 fs.updateClassifier() log.Printf("加载数据成功: %d个人员, %d个样本", len(fs.persons), len(fs.samples)) return nil } // 特征向量转换方法 func descriptorToString(d face.Descriptor) string { b := (*[128 * 4]byte)(unsafe.Pointer(&d)) return base64.StdEncoding.EncodeToString(b[:]) } func stringToDescriptor(s string) (face.Descriptor, error) { b, err := base64.StdEncoding.DecodeString(s) if err != nil { return face.Descriptor{}, err } if len(b) != 128*4 { return face.Descriptor{}, fmt.Errorf("invalid descriptor length: %d", len(b)) } return *(*face.Descriptor)(unsafe.Pointer(&b[0])), nil } // 计算人脸质量评分(简单实现) func (fs *FaceService) calculateFaceQuality(faceData face.Face) float32 { // 基于人脸区域大小和位置计算质量评分 rect := faceData.Rectangle width := rect.Max.X - rect.Min.X height := rect.Max.Y - rect.Min.Y area := width * height // 面积越大,质量越高(简化评分) quality := float32(area) / 10000.0 if quality > 1.0 { quality = 1.0 } return quality } // 更新分类器 func (fs *FaceService) updateClassifier() { fs.classifierSamples = make([]face.Descriptor, 0) fs.classifierCats = make([]int32, 0) fs.classifierLabels = make([]string, 0) catID := int32(0) for _, person := range fs.persons { for _, sample := range person.Samples { descriptor, err := stringToDescriptor(sample.Descriptor) if err != nil { log.Printf("解析样本 %d 的特征向量失败: %v", sample.ID, err) continue } fs.classifierSamples = append(fs.classifierSamples, descriptor) fs.classifierCats = append(fs.classifierCats, catID) fs.classifierLabels = append(fs.classifierLabels, fmt.Sprintf("%d:%d", person.ID, sample.ID)) catID++ } } if len(fs.classifierSamples) > 0 { fs.recognizer.SetSamples(fs.classifierSamples, fs.classifierCats) log.Printf("分类器更新完成: %d个样本", len(fs.classifierSamples)) } } // RegisterPerson 人员登记接口 func (fs *FaceService) RegisterPerson(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { fs.sendErrorResponse(w, "只支持POST方法", http.StatusMethodNotAllowed) return } // 解析表单数据 err := r.ParseMultipartForm(fs.config.MaxFileSize) if err != nil { fs.sendErrorResponse(w, "解析表单失败", http.StatusBadRequest) return } // 获取姓名 name := r.FormValue("name") if name == "" { fs.sendErrorResponse(w, "姓名不能为空", http.StatusBadRequest) return } // 检查姓名是否已存在 fs.mu.RLock() for _, person := range fs.persons { if person.Name == name { fs.mu.RUnlock() fs.sendErrorResponse(w, "该姓名已存在", http.StatusConflict) return } } fs.mu.RUnlock() // 获取上传的图片文件 file, handler, err := r.FormFile("image") if err != nil { fs.sendErrorResponse(w, "获取图片文件失败", http.StatusBadRequest) return } defer file.Close() // 处理图片并创建样本 sample, err := fs.processImageFile(file, handler, 0) // personID为0,稍后更新 if err != nil { fs.sendErrorResponse(w, err.Error(), http.StatusBadRequest) return } // 创建人员记录 fs.mu.Lock() person := &Person{ ID: fs.nextPersonID, Name: name, Samples: []FaceSample{*sample}, Created: time.Now(), Updated: time.Now(), } // 更新样本的人员ID sample.PersonID = person.ID fs.persons[person.ID] = person fs.samples[sample.ID] = sample fs.nextPersonID++ fs.stats.TotalPersons++ fs.stats.TotalSamples++ fs.stats.RegistrationCount++ // 更新分类器 fs.updateClassifier() fs.mu.Unlock() // 保存数据 go fs.saveData() fs.sendSuccessResponse(w, "人员登记成功", map[string]interface{}{ "person_id": person.ID, "name": person.Name, "sample_id": sample.ID, "image_url": sample.ImageURL, "quality": sample.Quality, }) } // AddSample 为已存在人员添加样本 func (fs *FaceService) AddSample(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { fs.sendErrorResponse(w, "只支持POST方法", http.StatusMethodNotAllowed) return } vars := mux.Vars(r) personIDStr := vars["person_id"] personID, err := strconv.Atoi(personIDStr) if err != nil { fs.sendErrorResponse(w, "无效的人员ID", http.StatusBadRequest) return } // 检查人员是否存在 fs.mu.RLock() person, exists := fs.persons[personID] if !exists { fs.mu.RUnlock() fs.sendErrorResponse(w, "人员不存在", http.StatusNotFound) return } fs.mu.RUnlock() // 解析表单数据 err = r.ParseMultipartForm(fs.config.MaxFileSize) if err != nil { fs.sendErrorResponse(w, "解析表单失败", http.StatusBadRequest) return } // 获取上传的图片文件 file, handler, err := r.FormFile("image") if err != nil { fs.sendErrorResponse(w, "获取图片文件失败", http.StatusBadRequest) return } defer file.Close() // 处理图片并创建样本 sample, err := fs.processImageFile(file, handler, personID) if err != nil { fs.sendErrorResponse(w, err.Error(), http.StatusBadRequest) return } // 添加样本 fs.mu.Lock() person.Samples = append(person.Samples, *sample) person.Updated = time.Now() fs.samples[sample.ID] = sample fs.stats.TotalSamples++ // 更新分类器 fs.updateClassifier() fs.mu.Unlock() // 保存数据 go fs.saveData() fs.sendSuccessResponse(w, "样本添加成功", map[string]interface{}{ "person_id": personID, "sample_id": sample.ID, "image_url": sample.ImageURL, "quality": sample.Quality, "total_samples": len(person.Samples), }) } // processImageFile 处理上传的图片文件 func (fs *FaceService) processImageFile(file io.Reader, handler *multipart.FileHeader, personID int) (*FaceSample, error) { // 生成文件名 ext := filepath.Ext(handler.Filename) if ext == "" { ext = ".jpg" } fs.mu.Lock() sampleID := fs.nextSampleID fs.nextSampleID++ fs.mu.Unlock() savedImagePath := filepath.Join(fs.config.UploadsDir, fmt.Sprintf("sample_%d%s", sampleID, ext)) imageURL := fmt.Sprintf("/uploads/sample_%d%s", sampleID, ext) // 保存图片文件 dst, err := os.Create(savedImagePath) if err != nil { return nil, fmt.Errorf("保存图片失败: %v", err) } defer dst.Close() _, err = io.Copy(dst, file) if err != nil { os.Remove(savedImagePath) return nil, fmt.Errorf("保存图片失败: %v", err) } // 人脸识别 faceResult, err := fs.recognizer.RecognizeSingleFile(savedImagePath) if err != nil { os.Remove(savedImagePath) var imageLoadError face.ImageLoadError if errors.As(err, &imageLoadError) { return nil, fmt.Errorf("图片格式不支持或已损坏") } return nil, fmt.Errorf("人脸识别失败: %v", err) } if faceResult == nil { os.Remove(savedImagePath) return nil, fmt.Errorf("未检测到人脸") } // 计算人脸质量 quality := fs.calculateFaceQuality(*faceResult) // 创建样本 sample := &FaceSample{ ID: sampleID, PersonID: personID, Descriptor: descriptorToString(faceResult.Descriptor), ImagePath: savedImagePath, ImageURL: imageURL, Quality: quality, Created: time.Now(), } return sample, nil } // RecognizeFace 人脸识别接口 func (fs *FaceService) RecognizeFace(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { fs.sendErrorResponse(w, "只支持POST方法", http.StatusMethodNotAllowed) return } // 解析表单数据 err := r.ParseMultipartForm(fs.config.MaxFileSize) if err != nil { fs.sendErrorResponse(w, "解析表单失败", http.StatusBadRequest) return } // 获取阈值参数 threshold := fs.config.DefaultThreshold if thresholdStr := r.FormValue("threshold"); thresholdStr != "" { if t, err := strconv.ParseFloat(thresholdStr, 32); err == nil { threshold = float32(t) } } // 处理上传的图片 tempFile, err := fs.saveTemporaryFile(r) if err != nil { fs.sendErrorResponse(w, err.Error(), http.StatusBadRequest) return } defer os.Remove(tempFile) fs.mu.RLock() if len(fs.persons) == 0 { fs.mu.RUnlock() fs.sendErrorResponse(w, "暂无已登记的人脸数据", http.StatusBadRequest) return } fs.mu.RUnlock() // 识别人脸 detectedFace, err := fs.recognizer.RecognizeSingleFile(tempFile) if err != nil { var imageLoadError face.ImageLoadError if errors.As(err, &imageLoadError) { fs.sendErrorResponse(w, "图片格式不支持或已损坏", http.StatusBadRequest) } else { fs.sendErrorResponse(w, "人脸识别失败", http.StatusInternalServerError) } return } if detectedFace == nil { fs.sendErrorResponse(w, "未检测到人脸", http.StatusBadRequest) return } // 执行分类 result := fs.classifyFace(detectedFace.Descriptor, threshold) fs.mu.Lock() fs.stats.RecognitionCount++ fs.mu.Unlock() go fs.saveData() if result == nil { fs.sendSuccessResponse(w, "未找到匹配的人脸", map[string]interface{}{ "recognized": false, }) } else { fs.sendSuccessResponse(w, "人脸识别成功", map[string]interface{}{ "recognized": true, "result": result, }) } } // RecognizeMultipleFaces 多人脸识别接口 func (fs *FaceService) RecognizeMultipleFaces(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { fs.sendErrorResponse(w, "只支持POST方法", http.StatusMethodNotAllowed) return } // 解析表单数据 err := r.ParseMultipartForm(fs.config.MaxFileSize) if err != nil { fs.sendErrorResponse(w, "解析表单失败", http.StatusBadRequest) return } // 获取阈值参数 threshold := fs.config.DefaultThreshold if thresholdStr := r.FormValue("threshold"); thresholdStr != "" { if t, err := strconv.ParseFloat(thresholdStr, 32); err == nil { threshold = float32(t) } } // 处理上传的图片 tempFile, err := fs.saveTemporaryFile(r) if err != nil { fs.sendErrorResponse(w, err.Error(), http.StatusBadRequest) return } defer os.Remove(tempFile) fs.mu.RLock() if len(fs.persons) == 0 { fs.mu.RUnlock() fs.sendErrorResponse(w, "暂无已登记的人脸数据", http.StatusBadRequest) return } fs.mu.RUnlock() // 识别所有人脸 faces, err := fs.recognizer.RecognizeFile(tempFile) if err != nil { var imageLoadError face.ImageLoadError if errors.As(err, &imageLoadError) { fs.sendErrorResponse(w, "图片格式不支持或已损坏", http.StatusBadRequest) } else { fs.sendErrorResponse(w, "人脸识别失败", http.StatusInternalServerError) } return } if len(faces) == 0 { fs.sendErrorResponse(w, "未检测到人脸", http.StatusBadRequest) return } // 处理每个检测到的人脸 var detections []FaceDetection for i, detectedFace := range faces { detection := FaceDetection{ Index: i, Rectangle: map[string]int{ "left": detectedFace.Rectangle.Min.X, "top": detectedFace.Rectangle.Min.Y, "right": detectedFace.Rectangle.Max.X, "bottom": detectedFace.Rectangle.Max.Y, }, } // 尝试识别 result := fs.classifyFace(detectedFace.Descriptor, threshold) if result != nil { detection.Recognized = true detection.Result = result } else { detection.Recognized = false detection.Message = "未找到匹配的人脸" } detections = append(detections, detection) } fs.mu.Lock() fs.stats.RecognitionCount++ fs.mu.Unlock() go fs.saveData() fs.sendSuccessResponse(w, fmt.Sprintf("检测到%d张人脸", len(faces)), detections) } // classifyFace 分类人脸 func (fs *FaceService) classifyFace(descriptor face.Descriptor, threshold float32) *RecognitionResult { fs.mu.RLock() defer fs.mu.RUnlock() if len(fs.classifierSamples) == 0 { return nil } catID := fs.recognizer.ClassifyThreshold(descriptor, threshold) if catID < 0 || catID >= len(fs.classifierLabels) { return nil } // 解析标签(格式:personID:sampleID) label := fs.classifierLabels[catID] var personID, sampleID int if n, err := fmt.Sscanf(label, "%d:%d", &personID, &sampleID); n != 2 || err != nil { return nil } person, personExists := fs.persons[personID] sample, sampleExists := fs.samples[sampleID] if !personExists || !sampleExists { return nil } // 计算相似度 sampleDescriptor, err := stringToDescriptor(sample.Descriptor) if err != nil { return nil } distance := fs.calculateDistance(descriptor, sampleDescriptor) confidence := (1 - distance) * 100 if confidence < 0 { confidence = 0 } return &RecognitionResult{ PersonID: person.ID, PersonName: person.Name, Confidence: confidence, Distance: distance, SampleID: sample.ID, } } // calculateDistance 计算欧几里得距离 func (fs *FaceService) calculateDistance(desc1, desc2 face.Descriptor) float32 { var sum float64 for i := 0; i < len(desc1); i++ { diff := float64(desc1[i] - desc2[i]) sum += diff * diff } return float32(math.Sqrt(sum)) } // saveTemporaryFile 保存临时文件 func (fs *FaceService) saveTemporaryFile(r *http.Request) (string, error) { file, handler, err := r.FormFile("image") if err != nil { return "", fmt.Errorf("获取图片文件失败: %v", err) } defer file.Close() tempFile := filepath.Join(fs.config.TempDir, fmt.Sprintf("temp_%d_%s", time.Now().UnixNano(), handler.Filename)) dst, err := os.Create(tempFile) if err != nil { return "", fmt.Errorf("创建临时文件失败: %v", err) } defer dst.Close() _, err = io.Copy(dst, file) if err != nil { os.Remove(tempFile) return "", fmt.Errorf("保存图片失败: %v", err) } return tempFile, nil } // GetPersonList 获取人员列表 func (fs *FaceService) GetPersonList(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { fs.sendErrorResponse(w, "只支持GET方法", http.StatusMethodNotAllowed) return } fs.mu.RLock() defer fs.mu.RUnlock() var personList []map[string]interface{} for _, person := range fs.persons { personInfo := map[string]interface{}{ "id": person.ID, "name": person.Name, "sample_count": len(person.Samples), "created": person.Created, "updated": person.Updated, } // 添加样本信息 var samples []map[string]interface{} for _, sample := range person.Samples { samples = append(samples, map[string]interface{}{ "id": sample.ID, "image_url": sample.ImageURL, "quality": sample.Quality, "created": sample.Created, }) } personInfo["samples"] = samples personList = append(personList, personInfo) } fs.sendSuccessResponse(w, "获取成功", personList) } // DeletePerson 删除人员 func (fs *FaceService) DeletePerson(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodDelete { fs.sendErrorResponse(w, "只支持DELETE方法", http.StatusMethodNotAllowed) return } vars := mux.Vars(r) personIDStr := vars["person_id"] personID, err := strconv.Atoi(personIDStr) if err != nil { fs.sendErrorResponse(w, "无效的人员ID", http.StatusBadRequest) return } fs.mu.Lock() defer fs.mu.Unlock() person, exists := fs.persons[personID] if !exists { fs.sendErrorResponse(w, "人员不存在", http.StatusNotFound) return } // 删除所有样本文件和数据 for _, sample := range person.Samples { if sample.ImagePath != "" { if err := os.Remove(sample.ImagePath); err != nil { log.Printf("删除图片文件失败: %v", err) } } delete(fs.samples, sample.ID) fs.stats.TotalSamples-- } // 删除人员数据 delete(fs.persons, personID) fs.stats.TotalPersons-- // 更新分类器 fs.updateClassifier() // 保存数据 go fs.saveData() fs.sendSuccessResponse(w, "删除成功", nil) } // DeleteSample 删除样本 func (fs *FaceService) DeleteSample(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodDelete { fs.sendErrorResponse(w, "只支持DELETE方法", http.StatusMethodNotAllowed) return } vars := mux.Vars(r) sampleIDStr := vars["sample_id"] sampleID, err := strconv.Atoi(sampleIDStr) if err != nil { fs.sendErrorResponse(w, "无效的样本ID", http.StatusBadRequest) return } fs.mu.Lock() defer fs.mu.Unlock() sample, exists := fs.samples[sampleID] if !exists { fs.sendErrorResponse(w, "样本不存在", http.StatusNotFound) return } person, personExists := fs.persons[sample.PersonID] if !personExists { fs.sendErrorResponse(w, "关联的人员不存在", http.StatusNotFound) return } // 检查是否为最后一个样本 if len(person.Samples) <= 1 { fs.sendErrorResponse(w, "不能删除最后一个样本,请删除整个人员", http.StatusBadRequest) return } // 删除图片文件 if sample.ImagePath != "" { if err := os.Remove(sample.ImagePath); err != nil { log.Printf("删除图片文件失败: %v", err) } } // 从人员的样本列表中移除 for i, s := range person.Samples { if s.ID == sampleID { person.Samples = append(person.Samples[:i], person.Samples[i+1:]...) break } } person.Updated = time.Now() // 删除样本数据 delete(fs.samples, sampleID) fs.stats.TotalSamples-- // 更新分类器 fs.updateClassifier() // 保存数据 go fs.saveData() fs.sendSuccessResponse(w, "样本删除成功", nil) } // GetStatistics 获取统计信息 func (fs *FaceService) GetStatistics(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { fs.sendErrorResponse(w, "只支持GET方法", http.StatusMethodNotAllowed) return } fs.mu.RLock() stats := fs.stats fs.mu.RUnlock() fs.sendSuccessResponse(w, "获取统计信息成功", stats) } // GetPersonDetail 获取人员详情 func (fs *FaceService) GetPersonDetail(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { fs.sendErrorResponse(w, "只支持GET方法", http.StatusMethodNotAllowed) return } vars := mux.Vars(r) personIDStr := vars["person_id"] personID, err := strconv.Atoi(personIDStr) if err != nil { fs.sendErrorResponse(w, "无效的人员ID", http.StatusBadRequest) return } fs.mu.RLock() person, exists := fs.persons[personID] fs.mu.RUnlock() if !exists { fs.sendErrorResponse(w, "人员不存在", http.StatusNotFound) return } // 构建详细信息 personDetail := map[string]interface{}{ "id": person.ID, "name": person.Name, "sample_count": len(person.Samples), "created": person.Created, "updated": person.Updated, } // 添加样本详情 var samples []map[string]interface{} for _, sample := range person.Samples { samples = append(samples, map[string]interface{}{ "id": sample.ID, "image_url": sample.ImageURL, "quality": sample.Quality, "created": sample.Created, }) } personDetail["samples"] = samples fs.sendSuccessResponse(w, "获取人员详情成功", personDetail) } // UpdatePersonName 更新人员姓名 func (fs *FaceService) UpdatePersonName(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPut { fs.sendErrorResponse(w, "只支持PUT方法", http.StatusMethodNotAllowed) return } vars := mux.Vars(r) personIDStr := vars["person_id"] personID, err := strconv.Atoi(personIDStr) if err != nil { fs.sendErrorResponse(w, "无效的人员ID", http.StatusBadRequest) return } // 解析JSON请求体 var requestData struct { Name string `json:"name"` } if err := json.NewDecoder(r.Body).Decode(&requestData); err != nil { fs.sendErrorResponse(w, "解析请求数据失败", http.StatusBadRequest) return } if requestData.Name == "" { fs.sendErrorResponse(w, "姓名不能为空", http.StatusBadRequest) return } fs.mu.Lock() defer fs.mu.Unlock() person, exists := fs.persons[personID] if !exists { fs.sendErrorResponse(w, "人员不存在", http.StatusNotFound) return } // 检查新姓名是否已存在 for _, p := range fs.persons { if p.ID != personID && p.Name == requestData.Name { fs.sendErrorResponse(w, "该姓名已存在", http.StatusConflict) return } } // 更新姓名 person.Name = requestData.Name person.Updated = time.Now() // 保存数据 go fs.saveData() fs.sendSuccessResponse(w, "姓名更新成功", map[string]interface{}{ "id": person.ID, "name": person.Name, }) } // HealthCheck 健康检查 func (fs *FaceService) HealthCheck(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { fs.sendErrorResponse(w, "只支持GET方法", http.StatusMethodNotAllowed) return } fs.mu.RLock() totalPersons := len(fs.persons) totalSamples := len(fs.samples) fs.mu.RUnlock() fs.sendSuccessResponse(w, "服务正常", map[string]interface{}{ "status": "healthy", "total_persons": totalPersons, "total_samples": totalSamples, "timestamp": time.Now(), }) } // 响应辅助方法 func (fs *FaceService) sendSuccessResponse(w http.ResponseWriter, message string, data interface{}) { fs.sendResponse(w, true, message, data, http.StatusOK) } func (fs *FaceService) sendErrorResponse(w http.ResponseWriter, message string, statusCode int) { fs.sendResponse(w, false, message, nil, statusCode) } func (fs *FaceService) sendResponse(w http.ResponseWriter, success bool, message string, data interface{}, statusCode int) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(statusCode) response := Response{ Success: success, Message: message, Data: data, Timestamp: time.Now(), } json.NewEncoder(w).Encode(response) } // 加载配置 func loadConfig(configPath string) (*Config, error) { // 默认配置 config := &Config{ Port: ":8080", ModelsDir: "models", UploadsDir: "uploads", TempDir: "temp", DataFile: "face_data.json", MaxFileSize: 10 << 20, // 10MB DefaultThreshold: 0.6, LogLevel: "info", } // 如果配置文件存在,则加载配置 if _, err := os.Stat(configPath); err == nil { file, err := os.Open(configPath) if err != nil { return nil, fmt.Errorf("打开配置文件失败: %v", err) } defer file.Close() if err := json.NewDecoder(file).Decode(config); err != nil { return nil, fmt.Errorf("解析配置文件失败: %v", err) } } return config, nil } // 定期保存数据的后台任务 func (fs *FaceService) startAutoSave() { go func() { ticker := time.NewTicker(5 * time.Minute) // 每5分钟保存一次 defer ticker.Stop() for range ticker.C { if err := fs.saveData(); err != nil { log.Printf("自动保存数据失败: %v", err) } } }() } func main() { // 加载配置 configPath := "config.json" if len(os.Args) > 1 { configPath = os.Args[1] } config, err := loadConfig(configPath) if err != nil { log.Printf("加载配置失败,使用默认配置: %v", err) config = &Config{ Port: ":8080", ModelsDir: "models", UploadsDir: "uploads", TempDir: "temp", DataFile: "face_data.json", MaxFileSize: 10 << 20, DefaultThreshold: 0.6, LogLevel: "info", } } // 初始化人脸识别服务 faceService, err := NewFaceService(config) if err != nil { log.Fatal("初始化人脸识别服务失败:", err) } defer faceService.Close() // 启动自动保存 faceService.startAutoSave() // 创建路由 r := mux.NewRouter() // API路由 api := r.PathPrefix("/api/v1").Subrouter() // 人员管理 api.HandleFunc("/person/register", faceService.RegisterPerson).Methods("POST") api.HandleFunc("/person/list", faceService.GetPersonList).Methods("GET") api.HandleFunc("/person/{person_id}", faceService.GetPersonDetail).Methods("GET") api.HandleFunc("/person/{person_id}", faceService.UpdatePersonName).Methods("PUT") api.HandleFunc("/person/{person_id}", faceService.DeletePerson).Methods("DELETE") // 样本管理 api.HandleFunc("/person/{person_id}/sample", faceService.AddSample).Methods("POST") api.HandleFunc("/sample/{sample_id}", faceService.DeleteSample).Methods("DELETE") // 识别接口 api.HandleFunc("/face/recognize", faceService.RecognizeFace).Methods("POST") api.HandleFunc("/face/recognize-multiple", faceService.RecognizeMultipleFaces).Methods("POST") // 统计和健康检查 api.HandleFunc("/statistics", faceService.GetStatistics).Methods("GET") api.HandleFunc("/health", faceService.HealthCheck).Methods("GET") // 静态文件服务 r.PathPrefix("/uploads/").Handler(http.StripPrefix("/uploads/", http.FileServer(http.Dir(config.UploadsDir)))) // CORS中间件 r.Use(func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") if r.Method == "OPTIONS" { w.WriteHeader(http.StatusOK) return } next.ServeHTTP(w, r) }) }) // 启动服务 fmt.Printf("=== 人脸识别服务启动 ===\n") fmt.Printf("端口: %s\n", config.Port) fmt.Printf("模型路径: %s\n", config.ModelsDir) fmt.Printf("上传目录: %s\n", config.UploadsDir) fmt.Printf("数据文件: %s\n", config.DataFile) fmt.Printf("默认阈值: %.2f\n", config.DefaultThreshold) fmt.Printf("最大文件大小: %d MB\n", config.MaxFileSize/(1024*1024)) fmt.Println("\n=== API接口列表 ===") fmt.Println("人员管理:") fmt.Println(" POST /api/v1/person/register - 人员登记") fmt.Println(" GET /api/v1/person/list - 获取人员列表") fmt.Println(" GET /api/v1/person/{id} - 获取人员详情") fmt.Println(" PUT /api/v1/person/{id} - 更新人员姓名") fmt.Println(" DELETE /api/v1/person/{id} - 删除人员") fmt.Println("\n样本管理:") fmt.Println(" POST /api/v1/person/{id}/sample - 添加样本") fmt.Println(" DELETE /api/v1/sample/{id} - 删除样本") fmt.Println("\n识别接口:") fmt.Println(" POST /api/v1/face/recognize - 单人脸识别") fmt.Println(" POST /api/v1/face/recognize-multiple - 多人脸识别") fmt.Println("\n系统接口:") fmt.Println(" GET /api/v1/statistics - 获取统计信息") fmt.Println(" GET /api/v1/health - 健康检查") fmt.Println("\n静态文件:") fmt.Println(" GET /uploads/* - 图片文件访问") log.Fatal(http.ListenAndServe(config.Port, r)) 配置文件 (config.json) ...

2025-06-24 · 31 分钟 · czyt

通过比较两个 Go pprof文件来发现性能问题【译】

原文链接 https://www.dolthub.com/blog/2025-06-20-go-pprof-diffing/ 我们正在努力进行兼容性工作 Doltgres ,世界上首个且唯一的版本控制兼容 Postgres 的 SQL 数据库。这意味着它能够开箱即用地与 Postgres 兼容的每一款库和工具协同工作。近来我们投入了大量精力在 SQLAlchemy 上,这是一款流行的 Python ORM。他们的 MySQL 集成与 Dolt 配合完美无缺,但他们的 Postgres 版本显然完全不同,严重依赖 pg_catalog 表。一位客户尝试使用后发现存在许多空白 ,因为 Doltgres 未将系统表(例如 dolt_log)包含在 pg_catalog 表中。所以我修复了这个问题,但这导致我们其中一个测试套件出现了神秘的性能退化,速度慢了 3 倍。 费了相当大的心思才弄清楚为什么这样一个看似无害的更改会导致性能出现如此巨大的差异。最终,最有帮助的是一个令人惊叹的 Go 工具链工具:使用 -base 选项来可视化两个性能分析之间的差异。 pprof 使用 pprof 对两个 profile 进行差异比较 Go 自带了一个强大的性能分析工具,pprof。与某些其他语言不同,您必须在代码中显式启用它才能获取性能分析结果;您不能事后进行或使用命令行标志。这很简单,但您必须编写代码来实现它。在我们的案例中,我将它直接放置在被分析的性能测试方法中。 func TestRegressionTests(t *testing.T) { // We'll only run this on GitHub Actions, so set this environment variable to run locally if _, ok := os.LookupEnv("REGRESSION_TESTING"); !ok { // t.Skip() } p := profile.Start(profile.CPUProfile) defer p.Stop() 这段代码的最后两行启动了一个 CPU 分析文件,然后在方法完成时停止它。它使用了 github.com/pkg/profile 包,该包提供了更便捷的内置分析器库封装。如果你运行这段代码,你会看到类似以下输出的行: ...

2025-06-21 · 2 分钟 · czyt

Git Submodule Update 命令备忘笔记

核心命令差异分析 git submodule update 行为:将子模块检出到父仓库记录的特定提交(commit hash) 特点:保持与父仓库 .gitmodules 和索引中记录的版本一致 适用场景:需要精确重现项目某个时间点的状态 局限性:不会拉取远程最新提交,只同步到父仓库索引中记录的版本 git submodule update --remote --recursive 行为:将子模块更新到远程分支的最新提交 特点:忽略父仓库记录的提交,直接拉取远程最新版本 适用场景:需要获取子模块的最新开发进度 常见困惑:为什么普通 update 拉取不到最新版本? 问题现象 许多开发者会遇到这种情况: # 执行普通更新,发现子模块没有更新到最新版本 git submodule update # 但是使用 --remote 参数却能拉取到最新版本 git submodule update --remote --recursive 根本原因分析 这个现象的核心在于 Git 子模块的版本绑定机制: 父仓库记录的是具体提交:当你添加子模块时,父仓库会在其索引中记录子模块的具体 commit hash 普通 update 遵循绑定版本:git submodule update 只会将子模块检出到父仓库记录的那个具体提交 –remote 忽略绑定版本:git submodule update --remote 会直接从远程仓库拉取最新提交 实际演示场景 # 查看当前子模块状态 git submodule status # 输出示例:-a1b2c3d4 path/to/submodule (v1.0-5-ga1b2c3d) # 父仓库记录的是 a1b2c3d4 这个提交 # 即使远程仓库已经有新的提交 e5f6g7h8 # 使用普通 update git submodule update # 子模块仍然停留在 a1b2c3d4 # 使用 --remote 参数 git submodule update --remote # 子模块更新到最新的 e5f6g7h8 详细参数说明 --remote 参数 # 更新到远程分支最新版本 git submodule update --remote # 指定远程分支 git submodule update --remote --branch main --recursive 参数 # 递归更新嵌套的子模块 git submodule update --recursive # 组合使用 git submodule update --remote --recursive 其他常用参数 # 强制更新(丢弃本地修改) git submodule update --force # 初始化并更新 git submodule update --init --recursive # 并行更新(提高速度) git submodule update --jobs 4 配置选项管理 局部配置(仅当前仓库) 设置子模块默认更新行为 # 设置特定子模块跟踪远程分支 git config -f .gitmodules submodule.<submodule-name>.branch <branch-name> # 设置子模块更新策略为 rebase git config -f .gitmodules submodule.<submodule-name>.update rebase # 设置子模块更新策略为 merge git config -f .gitmodules submodule.<submodule-name>.update merge 配置子模块远程 URL # 修改子模块远程 URL git config -f .gitmodules submodule.<submodule-name>.url <new-url> # 同步配置到 .git/config git submodule sync 设置递归默认行为 # 设置默认递归更新 git config submodule.recurse true # 设置 fetch 时自动递归 git config fetch.recurseSubmodules true 全局配置(影响所有仓库) 设置全局递归行为 # 全局启用子模块递归 git config --global submodule.recurse true # 全局设置 fetch 递归 git config --global fetch.recurseSubmodules true # 全局设置 push 递归检查 git config --global push.recurseSubmodules check 设置全局并行任务数 # 设置全局并行更新任务数 git config --global submodule.fetchJobs 4 实际操作示例 场景1:初次克隆带子模块的仓库 # 方法1:克隆时初始化子模块 git clone --recursive <repository-url> # 方法2:克隆后初始化子模块 git clone <repository-url> cd <repository> git submodule init git submodule update 场景2:定期同步子模块 # 同步到父仓库记录的版本 git pull git submodule update # 获取子模块最新版本 git submodule update --remote --recursive 场景3:子模块版本管理 # 查看子模块状态 git submodule status # 查看子模块差异 git diff --cached --submodule # 提交子模块版本更新 git add <submodule-path> git commit -m "Update submodule to latest version" 典型问题解决:子模块版本同步 问题:子模块无法获取最新版本 症状:执行 git submodule update 后,子模块版本没有更新到最新 ...

2025-06-17 · 4 分钟 · czyt

Claude Code使用笔记

来自https://x.com/shao__meng/status/1950196917595754662的ClaudeCode技巧 安装 你需要有nodejs这样的环境,bun没进行测试.需要安装npm或者pnpm包管理器 安装 Claude Code Homebrew brew install --cask claude-code Winget winget install Anthropics.ClaudeCode macOS, Linux, WSL curl -fsSL https://claude.ai/install.sh | bash Windows PowerShell irm https://claude.ai/install.ps1 | iex Windows CMD curl -fsSL https://claude.ai/install.cmd -o install.cmd && install.cmd && del install.cmd deno环境 安装Deno curl -fsSL https://deno.land/install.sh | sh 全局按照Claude code deno install --global -A npm:@anthropic-ai/claude-code 将 ~/.deno/bin 添加到PATH claude --version 1.0.65 (Claude Code) Claude Code Router使用 虽说Claude Code 很好用,但是国内很难稳定直连Claude,手上的硅基余额还有上百块,于是突发奇想,如何把手上的硅基DeepSeek用到Claude Code上。找了下,发现Claude Code Router 这个项目。安装方法: pnpm install -g @musistudio/claude-code-router 配置 Claude Code 正常使用,直接登录即可。也可以参考某claude code镜像提供商家的代码 ...

2025-06-12 · 10 分钟 · czyt

Go语言中的指数加权移动平均

Tailscale中有很多实用的代码,下面是EWMA的一个实现,源码 // Copyright (c) Tailscale Inc & AUTHORS // SPDX-License-Identifier: BSD-3-Clause // Package maths contains additional mathematical functions or structures not // found in the standard library. package maths import ( "math" "time" ) // EWMA is an exponentially weighted moving average supporting updates at // irregular intervals with at most nanosecond resolution. // The zero value will compute a half-life of 1 second. // It is not safe for concurrent use. // TODO(raggi): de-duplicate with tstime/rate.Value, which has a more complex // and synchronized interface and does not provide direct access to the stable // value. type EWMA struct { value float64 // current value of the average lastTime int64 // time of last update in unix nanos halfLife float64 // half-life in seconds } // NewEWMA creates a new EWMA with the specified half-life. If halfLifeSeconds // is 0, it defaults to 1. func NewEWMA(halfLifeSeconds float64) *EWMA { return &EWMA{ halfLife: halfLifeSeconds, } } // Update adds a new sample to the average. If t is zero or precedes the last // update, the update is ignored. func (e *EWMA) Update(value float64, t time.Time) { if t.IsZero() { return } hl := e.halfLife if hl == 0 { hl = 1 } tn := t.UnixNano() if e.lastTime == 0 { e.value = value e.lastTime = tn return } dt := (time.Duration(tn-e.lastTime) * time.Nanosecond).Seconds() if dt < 0 { // drop out of order updates return } // decay = 2^(-dt/halfLife) decay := math.Exp2(-dt / hl) e.value = e.value*decay + value*(1-decay) e.lastTime = tn } // Get returns the current value of the average func (e *EWMA) Get() float64 { return e.value } // Reset clears the EWMA to its initial state func (e *EWMA) Reset() { e.value = 0 e.lastTime = 0 } 这个库实现了 EWMA(指数加权移动平均),是一种用来平滑数据波动的数学工具。让我用简单的方式解释一下: ...

2025-06-06 · 5 分钟 · czyt

在go中使用Semaphoregroup

在netbird 中看到一个semaphore-group 函数 package semaphoregroup import ( "context" "sync" ) // SemaphoreGroup is a custom type that combines sync.WaitGroup and a semaphore. type SemaphoreGroup struct { waitGroup sync.WaitGroup semaphore chan struct{} } // NewSemaphoreGroup creates a new SemaphoreGroup with the specified semaphore limit. func NewSemaphoreGroup(limit int) *SemaphoreGroup { return &SemaphoreGroup{ semaphore: make(chan struct{}, limit), } } // Add increments the internal WaitGroup counter and acquires a semaphore slot. func (sg *SemaphoreGroup) Add(ctx context.Context) { sg.waitGroup.Add(1) // Acquire semaphore slot select { case <-ctx.Done(): return case sg.semaphore <- struct{}{}: } } // Done decrements the internal WaitGroup counter and releases a semaphore slot. func (sg *SemaphoreGroup) Done(ctx context.Context) { sg.waitGroup.Done() // Release semaphore slot select { case <-ctx.Done(): return case <-sg.semaphore: } } // Wait waits until the internal WaitGroup counter is zero. func (sg *SemaphoreGroup) Wait() { sg.waitGroup.Wait() } 官方测试用例 ...

2025-06-05 · 3 分钟 · czyt

在 Go 中构建可扩展的多租户应用程序【译】

原文链接 https://atlasgo.io/blog/2025/05/26/gophercon-scalable-multi-tenant-apps-in-go 为 GopherCon Israel 2025 准备并呈现。 引言 在本篇博客中,我们将基于我们在构建 Atlas Cloud 后端(作为我们商业产品的一部分)的经验,探讨在 Go 中构建可扩展多租户应用程序的不同策略。 但首先,让我们明确一下我们所说的多租户应用是什么。 多租户是一个系统的特性,即单个实例为多个客户(租户)提供服务。 作为一家商业企业,你的目标当然是有很多客户!但你想要服务许多客户,他们期望有一个流畅无缝的体验,就好像只有他们在使用你的服务一样。 你向客户隐含做出的两个重要承诺是: 数据隔离:每个租户的数据都是隔离和安全的,确保一个租户无法访问另一个租户的数据。 性能:无论租户数量如何,应用程序都应表现良好,确保一个租户的使用不会降低其他租户的体验。 让我们探讨一些可能实现这些承诺的方法。 物理隔离 确保数据和性能隔离最直接的方法是为每个租户运行一个独立的应用实例。这种方法通常被称为“物理隔离”或“专用实例”。 为每个租户运行独立实例,可以确保: 每个租户的数据存储在独立的数据库中,从而保证完全隔离。如有需要,租户可以在不同的 VPC 中运行,甚至可以在不同的云账户中运行。 租户独立消费资源,因此一个租户的使用不会影响其他租户,从而消除了“吵闹邻居”问题。 然而,大多数公司不会选择这条路,原因有几点: 运营开销:将应用程序部署到数百或数千个生产环境,每个环境都有自己的数据库和配置,管理起来可能非常复杂。 成本:如果您的公司需要为每个租户的资源支付云服务提供商的费用,成本可能会迅速变得难以承受。 可扩展性:如果添加新租户需要部署新实例,那么扩展应用程序以支持许多租户可能会成为瓶颈。 可见性:跨多个实例监控和调试问题可能具有挑战性,因为您需要从所有实例中聚合日志和指标。 逻辑隔离 另一种方法是运行单个应用程序实例来服务多个租户,通常称为“逻辑隔离”。在此模型中,租户共享相同的应用程序代码和数据库,但它们的数据在逻辑上是隔离的。逻辑隔离可以总结为: 共享基础设施,作用域请求 让我们看看在 Go 应用程序中实际如何使用这个示例,从一个简单的 GORM 示例开始: package main type Tenant struct { ID uint `gorm:"primaryKey" json:"id"` Name string `json:"name"` } type Customer struct { ID uint `gorm:"primaryKey" json:"id"` Name string `json:"name"` TenantID uint `json:"tenant_id"` } 在这个示例中,我们有两个模型: Tenant 和 Customer 。每个 Customer 属于一个 Tenant , Customer 模型中的 TenantID 字段用于将每个客户与特定的租户关联起来。 ...

2025-05-30 · 6 分钟 · czyt

TIL:Bash 脚本中的超时【译】

原文链接 https://heitorpb.github.io/bla/timeout/ 前几天在工作中,我们有一个 Bash 脚本,用于设置一个 Web 服务器,并等待它启动后再继续执行后续操作。这个脚本运行正常,我们也没有遇到任何问题,直到出现了一个无限循环。 我们使用 Bash 内置的 until 来检查 Web 服务器是否正常: until curl --silent --fail-with-body 10.0.0.1:8080/health; do sleep 1 done 这很好用。除非我们的 Web 服务器在启动过程中崩溃,并且我们 sleep 1 永远等待。 这里有一个实用的工具:timeout。顾名思义,这个命令可以为其他命令添加超时功能。您指定想要等待命令的时间限制,如果该时间已过,timeout 会发送一个信号来终止它,并以非零状态退出。默认情况下,timeout 发送的是 SIGTERM 信号,但您可以通过 --signal 标志来更改它,例如 timeout --signal=SIGKILL 1s foo 。 例如,timeout 1s sleep 5 将向 sleep 发送 SIGTERM 信号 1秒后: $ time timeout 1s sleep 4 real 0m1,004s user 0m0,000s sys 0m0,005s $ echo $? 124 那么接下来应该将 timeout 和 until 结合起来: timeout 1m until curl --silent --fail-with-body 10.0.0.1:8080/health; do sleep 1 done 唯一的问题是这行不通。timeout 需要一个可终止的命令,而 until 是一个 shell 关键字:你不能 SIGTERMuntil``。我们无法使用 timeout 与任何 shell 内建命令一起使用。 ...

2025-05-30 · 1 分钟 · czyt

Sherpa Go语言实战

简介 sherpa 是 Next-gen Kaldi 项目的部署框架。 使用 VAD 语音活动检测(Voice Activity Detection,简称VAD)是一种技术,用于检测音频信号中是否存在语音或其他声音活动。它在语音处理、语音识别、音频压缩等领域有广泛的应用。 VAD的主要功能 语音识别系统:通过VAD,系统可以在检测到语音时启动识别过程,提高效率。 音频压缩:在语音通信中,VAD可以帮助压缩算法仅对有效语音信号进行压缩,减少传输数据量。 噪声抑制系统:通过检测语音活动,系统可以在静默时段增强噪声抑制效果。 在GO中使用 todo KWS 关键词唤醒(Keyword Spotting,简称KWS)是一种技术,用于检测音频信号中特定的关键词或短语。它广泛应用于语音助手、智能家居设备、车载系统等领域,通过识别特定关键词来激活设备或执行特定命令。 主要功能 关键词检测:识别音频信号中是否包含预定义的关键词或短语。 唤醒设备:当检测到关键词时,激活设备或应用程序。 提高用户体验:通过语音命令简化操作流程,增强用户体验。 自定义keywords 通过官方的工具sherpa-onnx-cli,可以实现自定义关键字,下面是简单的介绍 原文 # Note: You need to run pip install sherpa-onnx to get the commandline tool: sherpa-onnx-cli sherpa-onnx-cli text2token --help Usage: sherpa-onnx-cli text2token [OPTIONS] INPUT OUTPUT Options: --text TEXT Path to the input texts. Each line in the texts contains the original phrase, it might also contain some extra items, for example, the boosting score (startting with :), the triggering threshold (startting with #, only used in keyword spotting task) and the original phrase (startting with @). Note: extra items will be kept in the output. example input 1 (tokens_type = ppinyin): 小爱同学 :2.0 #0.6 @小爱同学 你好问问 :3.5 @你好问问 小艺小艺 #0.6 @小艺小艺 example output 1: x iǎo ài tóng x ué :2.0 #0.6 @小爱同学 n ǐ h ǎo w èn w èn :3.5 @你好问问 x iǎo y ì x iǎo y ì #0.6 @小艺小艺 example input 2 (tokens_type = bpe): HELLO WORLD :1.5 #0.4 HI GOOGLE :2.0 #0.8 HEY SIRI #0.35 example output 2: ▁HE LL O ▁WORLD :1.5 #0.4 ▁HI ▁GO O G LE :2.0 #0.8 ▁HE Y ▁S I RI #0.35 --tokens TEXT The path to tokens.txt. --tokens-type TEXT The type of modeling units, should be cjkchar, bpe, cjkchar+bpe, fpinyin or ppinyin. fpinyin means full pinyin, each cjkchar has a pinyin(with tone). ppinyin means partial pinyin, it splits pinyin into initial and final, --bpe-model TEXT The path to bpe.model. Only required when tokens-type is bpe or cjkchar+bpe. --help Show this message and exit. 我这里只是记录下小爱同学 :2.0 #0.6 @小爱同学 这部分。在这个例子里面:数字 是增强得分(boosting score),#数字 是触发阈值(triggering threshold)。 ...

2025-03-20 · 6 分钟 · czyt