初始化项目

This commit is contained in:
BBIT-Kai
2026-05-26 11:51:57 +08:00
commit 6878f4ea5f
15 changed files with 4258 additions and 0 deletions
+94
View File
@@ -0,0 +1,94 @@
package log
import (
"fmt"
"os"
"path/filepath"
"time"
)
var logDir = "./logs" // 日志目录,可根据需要修改
// 初始化日志目录
func Init() {
if err := os.MkdirAll(logDir, 0755); err != nil {
fmt.Println("create log dir failed:", err)
}
cleanupOldLogs()
}
// Cleanup 删除超过7天的日志文件
func cleanupOldLogs() {
files, err := os.ReadDir(logDir)
if err != nil {
return
}
cutoff := time.Now().AddDate(0, 0, -7)
for _, f := range files {
if f.IsDir() {
continue
}
info, err := f.Info()
if err != nil {
continue
}
if info.ModTime().Before(cutoff) {
_ = os.Remove(filepath.Join(logDir, f.Name()))
}
}
}
// log 内部写文件
func logToFile(level, msg string) {
fmt.Println(msg)
t := time.Now()
// 确保日志目录存在
if err := os.MkdirAll(logDir, 0755); err != nil {
fmt.Println("create log dir failed:", err)
return
}
filename := filepath.Join(logDir, t.Format("2006-01-02")+".log")
f, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
fmt.Println("open log file failed:", err)
return
}
defer f.Close()
line := fmt.Sprintf("%s [%s] %s\n", t.Format("2006-01-02 15:04:05"), level, msg)
_, _ = f.WriteString(line)
}
// 对外接口
func Info(msg string) {
logToFile("INFO", msg)
}
// Println 支持多个参数拼接,写 INFO 日志
func Println(v ...interface{}) {
msg := fmt.Sprint(v...)
logToFile("INFO", msg)
}
func Warn(msg string) {
logToFile("WARN", msg)
}
func Error(msg string) {
logToFile("ERROR", msg)
}
func Fatal(err error) {
if err == nil {
return
}
logToFile("ERROR", err.Error())
}
// Fatal 打印错误日志并退出程序
func Fatalf(msg string, args ...interface{}) {
if len(args) > 0 {
msg = fmt.Sprintf(msg, args...)
}
logToFile("FATAL", msg)
os.Exit(1)
}
+83
View File
@@ -0,0 +1,83 @@
package main
import (
"context"
"fmt"
"sentinel/app/log"
"sentinel/app/model"
"sentinel/app/net"
"sentinel/app/storage"
)
func main() {
banner := `
==========================================================================
_______ _______ _ __________________ _ _______ _
( ____ \( ____ \( ( /|\__ __/\__ __/( ( /|( ____ \( \
| (_____ | (__ | \ | | | | | | | \ | || (__ | |
(_____ )| __) | (\ \) | | | | | | (\ \) || __) | |
/\____) || (____/\| ) \ | | | ___) (___| ) \ || (____/\| (____/\
\_______)(_______/|/ )_) )_( \_______/|/ )_)(_______/(_______/
==========================================================================
`
deviceID := "6823316af0e540bea818ec1be25cc14a"
log.Init() // 初始化日志目录
log.Info("Device id: " + deviceID) // 第一次启动记录
log.Info(banner)
// 0. 从海康SDK获取图片以及信息
record := loadData(deviceID)
// 1. 上传图片到OSS
uploadFile(record.LicensePlateImage, record.VehicleImage)
// 2. 调用分析请求
analytics(record)
<-make(chan struct{})
//fatal error: all goroutines are asleep - deadlock!
}
func loadData(deviceID string) model.Record {
return model.Record{
DeviceId: deviceID, //从环境变量取
LicensePlate: "晋A-888888",
LicensePlateImage: "licensePlateImage_test1.jpg",
VehicleType: "大型货车",
VehicleImage: "vehicleImage_test1.jpg",
}
}
func uploadFile(licensePlateImage string, vehicleImage string) {
if err := storage.Init(); err != nil {
log.Fatal(err)
}
// todo 需要压缩图片至1~3MB
size, err := storage.UploadFile(
context.Background(),
"sentinel",
"license_plate/"+licensePlateImage,
"tmp/"+licensePlateImage,
"image/jpeg",
)
if err != nil {
log.Fatal(err)
}
log.Println(fmt.Sprintf("车牌照已上传完毕, 大小: = %d KB", size/1024))
size, err = storage.UploadFile(
context.Background(),
"sentinel",
"vehicle_image/"+vehicleImage,
"tmp/"+vehicleImage,
"image/jpeg",
)
if err != nil {
log.Fatal(err)
}
log.Println(fmt.Sprintf("车身照已上传完毕, 大小: = %d KB", size/1024))
}
func analytics(record model.Record) {
err := api.Analytics(record)
if err != nil {
return
}
}
+7
View File
@@ -0,0 +1,7 @@
package model
type BaseResponse struct {
Status bool `json:"status"` // 是否成功
Message string `json:"message"` // 提示信息
Data interface{} `json:"data,omitempty"` // 泛型数据,用 interface{} 接收任意类型
}
+9
View File
@@ -0,0 +1,9 @@
package model
type Record struct {
DeviceId string
LicensePlate string
LicensePlateImage string
VehicleType string
VehicleImage string
}
+13
View File
@@ -0,0 +1,13 @@
package api
import (
"sentinel/app/model"
)
const (
analyticsURL = "/api/public/sentinel-record-analytics"
)
func Analytics(req model.Record) error {
return Post(analyticsURL, req, nil)
}
+102
View File
@@ -0,0 +1,102 @@
package api
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
"sentinel/app/log"
"sentinel/app/model"
"time"
)
// const baseURL = "http://127.0.0.1:13011"
const baseURL = "https://ai.ronsunny.cn:8090"
var client = &http.Client{
Timeout: 5 * time.Second,
}
func Get(path string, query map[string]string, out any) error {
return do(http.MethodGet, path, query, nil, out)
}
func Post(path string, body any, out any) error {
return do(http.MethodPost, path, nil, body, out)
}
func do(method, path string, query map[string]string, body any, out any) error {
u, err := url.Parse(baseURL + path)
if err != nil {
log.Error("parse url failed: " + err.Error())
return err
}
if len(query) > 0 {
q := u.Query()
for k, v := range query {
q.Set(k, v)
}
u.RawQuery = q.Encode()
}
var reqBody *bytes.Reader
if body != nil {
b, err := json.Marshal(body)
if err != nil {
log.Error("marshal body failed: " + err.Error())
return err
}
reqBody = bytes.NewReader(b)
} else {
reqBody = bytes.NewReader(nil)
}
req, err := http.NewRequest(method, u.String(), reqBody)
if err != nil {
log.Error("create request failed: " + err.Error())
return err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("apikey", "NzusyzcLIUoZ22tflHN2sOjHrry3W7zJ")
resp, err := client.Do(req)
if err != nil {
log.Error("request failed: " + err.Error())
return err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
log.Error("http status error: " + resp.Status)
return fmt.Errorf("http status %d", resp.StatusCode)
}
// 解析成 BaseResponse
var baseResp model.BaseResponse
if err := json.NewDecoder(resp.Body).Decode(&baseResp); err != nil {
log.Error("decode base response failed: " + err.Error())
return err
}
if !baseResp.Status {
log.Error("server returned error: " + baseResp.Message)
return fmt.Errorf(baseResp.Message)
}
if out != nil && baseResp.Data != nil {
// 将 Data 转成业务类型
b, err := json.Marshal(baseResp.Data)
if err != nil {
log.Error("marshal data failed: " + err.Error())
return err
}
if err := json.Unmarshal(b, out); err != nil {
log.Error("unmarshal data to out failed: " + err.Error())
return err
}
}
return nil
}
+62
View File
@@ -0,0 +1,62 @@
package storage
import (
"context"
"errors"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
)
const (
minioEndpoint = "ai.ronsunny.cn:9000"
minioAccessKey = "minioadmin"
minioSecretKey = "minioadmin"
useSSL = true
)
var client *minio.Client
func Init() error {
if client != nil {
return nil
}
c, err := minio.New(minioEndpoint, &minio.Options{
Creds: credentials.NewStaticV4(minioAccessKey, minioSecretKey, ""),
Secure: useSSL,
})
if err != nil {
return err
}
client = c
return nil
}
func UploadFile(
ctx context.Context,
bucketName string,
objectName string,
filePath string,
contentType string,
) (int64, error) {
if client == nil {
return 0, errors.New("minio client not initialized")
}
info, err := client.FPutObject(
ctx,
bucketName,
objectName,
filePath,
minio.PutObjectOptions{
ContentType: contentType,
},
)
if err != nil {
return 0, err
}
return info.Size, nil
}
+100
View File
@@ -0,0 +1,100 @@
package utils
import (
"fmt"
"net"
"os"
"runtime"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/disk"
"github.com/shirou/gopsutil/v3/mem"
)
func GetLocalIP() string {
ifaces, err := net.Interfaces()
if err != nil {
return ""
}
for _, iface := range ifaces {
if iface.Flags&net.FlagUp == 0 {
continue // 接口未启用
}
if iface.Flags&net.FlagLoopback != 0 {
continue // 忽略回环
}
addrs, err := iface.Addrs()
if err != nil {
continue
}
for _, addr := range addrs {
var ip net.IP
switch v := addr.(type) {
case *net.IPNet:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
if ip == nil || ip.IsLoopback() {
continue
}
ip = ip.To4()
if ip == nil {
continue
}
return ip.String()
}
}
return ""
}
func GetHostname() string {
name, err := os.Hostname()
if err != nil {
return ""
}
return name
}
func GetMacAddress() string {
ifaces, err := net.Interfaces()
if err != nil {
return ""
}
for _, iface := range ifaces {
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
continue
}
mac := iface.HardwareAddr.String()
if mac != "" {
return mac
}
}
return ""
}
func GetOSInfo() string {
return runtime.GOOS // windows, linux, darwin
}
func GetCPUInfo() string {
info, err := cpu.Info()
if err != nil || len(info) == 0 {
return ""
}
return info[0].ModelName
}
func GetMemory() string {
v, err := mem.VirtualMemory()
if err != nil {
return ""
}
return fmt.Sprintf("%dMB", v.Total/1024/1024)
}
func GetDisk() string {
usage, err := disk.Usage("/")
if err != nil {
return ""
}
return fmt.Sprintf("%dGB", usage.Total/1024/1024/1024)
}