You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
267 lines
7.7 KiB
267 lines
7.7 KiB
package api
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/golang-jwt/jwt/v4"
|
|
)
|
|
|
|
// 需要记录操作日志的重要接口(增删改类操作)
|
|
var auditableAPIMap = map[string]bool{
|
|
// 登录
|
|
"api_login": false,
|
|
// 管理员管理
|
|
"api_addadminuser": true,
|
|
"api_deladminuser": true,
|
|
"api_updateadminuser": true,
|
|
// 配置管理
|
|
"api_addconfig": true,
|
|
"api_updateconfig": true,
|
|
"api_delconfig": true,
|
|
"api_addglobalconfigs": true,
|
|
"api_updateglobalconfig": true,
|
|
"api_delglobalconfigs": true,
|
|
// 智能体
|
|
"api_addagent": true,
|
|
"api_updateagent": true,
|
|
"api_delagent": true,
|
|
// 产品管理
|
|
"api_addproduct": true,
|
|
"api_updateproduct": true,
|
|
"api_delproduct": true,
|
|
"api_addproductversion": true,
|
|
"api_delproductversion": true,
|
|
// 工厂管理
|
|
"api_addfactory": true,
|
|
"api_updatefactory": true,
|
|
"api_delfactory": true,
|
|
"api_createfactorydevics": true,
|
|
"api_restfactorydevics": true,
|
|
"api_resetlicensestatus": true,
|
|
// 商品管理
|
|
"api_addgoods": true,
|
|
"api_updategoods": true,
|
|
"api_delgoods": true,
|
|
// 渠道包
|
|
"api_addchannelapp": true,
|
|
"api_uploadchannelapp": true,
|
|
"api_delchannelapp": true,
|
|
// MCP
|
|
"api_addmcpservers": true,
|
|
"api_updatemcpservers": true,
|
|
"api_delmcpservers": true,
|
|
// 模板
|
|
"api_addtemplates": true,
|
|
"api_updatetemplates": true,
|
|
"api_deltemplate": true,
|
|
// 唤醒词
|
|
"api_addwakeupvoice": true,
|
|
"api_updatewakeupvoice": true,
|
|
"api_delwakeupvoice": true,
|
|
// 用户管理
|
|
"api_updateuser": true,
|
|
// 邮件
|
|
"api_sendsmtpemail": true,
|
|
// 资源分发
|
|
"api_dispatchresource": true,
|
|
"api_grantadminresource": true, // 超管→后台账号 资源点赠送
|
|
// 统计
|
|
"api_rebuildstats": true, // 全量重建统计
|
|
}
|
|
|
|
// 设置跨域
|
|
func cors() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
method := c.Request.Method
|
|
origin := c.Request.Header.Get("Origin")
|
|
if origin != "" {
|
|
c.Header("Access-Control-Allow-Origin", "*") // 可将将 * 替换为指定的域名
|
|
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
|
|
c.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization")
|
|
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type")
|
|
c.Header("Access-Control-Allow-Credentials", "true")
|
|
}
|
|
if method == "OPTIONS" {
|
|
c.AbortWithStatus(http.StatusNoContent)
|
|
}
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
func parseToken(tokenString string, secretKey []byte) (*jwt.RegisteredClaims, error) {
|
|
parsedToken, err := jwt.ParseWithClaims(tokenString, &jwt.RegisteredClaims{}, func(token *jwt.Token) (interface{}, error) {
|
|
// Ensure the token method conforms to "SigningMethodHMAC"
|
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
|
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
|
}
|
|
return secretKey, nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Validate the token and extract the claims
|
|
if claims, ok := parsedToken.Claims.(*jwt.RegisteredClaims); ok && parsedToken.Valid {
|
|
return claims, nil
|
|
} else {
|
|
return nil, fmt.Errorf("invalid token")
|
|
}
|
|
}
|
|
|
|
// 基准日期
|
|
var baseDate = time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
|
|
// createCode 将三个整数拼接成一个15位的十五进制字符串
|
|
func createCode(devicetype int, factoryid int, timeday int, number int) string {
|
|
// 转换并格式化channel为4位十五进制
|
|
deviceTsype := formatToHex(devicetype, 1)
|
|
// 转换并格式化group为4位十五进制
|
|
factoryId := formatToHex(factoryid, 2)
|
|
// 转换并格式化number为7位十五进制
|
|
timeDay := formatToHex(timeday, 4)
|
|
// 转换并格式化number为7位十五进制
|
|
numBer := formatToHex(number, 4)
|
|
return deviceTsype + factoryId + timeDay + numBer
|
|
}
|
|
|
|
// GenerateCustomMAC 生成自定义MAC地址(4+8+16+20位分配)
|
|
// devicetype: 设备类型 (0-15, 4位)
|
|
// factoryid: 厂家ID (0-255, 8位)
|
|
// batchNumber: 批次数据 (如1.2版本号,直接存储,0-65535, 16位)
|
|
// serial: 生产编号 (0-1048575, 20位)
|
|
func GenerateCustomMAC(productid uint16, batchNumber byte, serial uint32) (code string, err error) {
|
|
// 参数验证
|
|
if productid > 0xFFFF {
|
|
err = fmt.Errorf("productid must be <= 0xFFFF (16 bits)")
|
|
return
|
|
}
|
|
if batchNumber > 0xFF {
|
|
err = fmt.Errorf("batchNumber must be <= 0xFF (8 bits)")
|
|
return
|
|
}
|
|
if serial > 0xFFFFFF {
|
|
err = fmt.Errorf("serial must be <= 0xFFFFFF (24 bits)")
|
|
return
|
|
}
|
|
|
|
mac := make([]byte, 6)
|
|
|
|
// 第一个字节:productid的高8位
|
|
mac[0] = byte((productid >> 8) & 0xFF)
|
|
|
|
// 第二个字节:productid的低8位
|
|
mac[1] = byte(productid & 0xFF)
|
|
|
|
// 第三个字节:批次数据(1个字节)
|
|
mac[2] = byte(batchNumber & 0xFF)
|
|
|
|
// 第四个字节:生产编号的高8位
|
|
mac[3] = byte((serial >> 16) & 0xFF)
|
|
|
|
// 第五个字节:生产编号的中间8位
|
|
mac[4] = byte((serial >> 8) & 0xFF)
|
|
|
|
// 第六个字节:生产编号的低8位
|
|
mac[5] = byte(serial & 0xFF)
|
|
|
|
// 生成MAC地址字符串
|
|
code = fmt.Sprintf("%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5])
|
|
return
|
|
}
|
|
|
|
// ParseCustomMAC 解析自定义MAC地址并提取原始数据
|
|
// 返回: productid, batchNumber, serial, error
|
|
func ParseCustomMAC(macCode string) (productid uint16, batchNumber byte, serial uint32, err error) {
|
|
// 验证MAC地址格式(12个十六进制字符)
|
|
if len(macCode) != 17 { // 包含冒号的格式如 AA:BB:CC:DD:EE:FF
|
|
// 尝试解析没有冒号的格式
|
|
if len(macCode) != 12 {
|
|
err = fmt.Errorf("MAC地址长度错误,期望12字符或17字符(带冒号),实际%d字符", len(macCode))
|
|
return
|
|
}
|
|
} else {
|
|
// 移除冒号
|
|
macCode = strings.ReplaceAll(macCode, ":", "")
|
|
}
|
|
|
|
// 检查是否为有效的十六进制字符
|
|
for i, char := range macCode {
|
|
if !((char >= '0' && char <= '9') || (char >= 'A' && char <= 'F') || (char >= 'a' && char <= 'f')) {
|
|
err = fmt.Errorf("第%d位包含无效字符: %c", i+1, char)
|
|
return
|
|
}
|
|
}
|
|
|
|
// 解析MAC地址的6个字节
|
|
mac := make([]byte, 6)
|
|
for i := 0; i < 6; i++ {
|
|
value, parseErr := strconv.ParseUint(macCode[i*2:i*2+2], 16, 8)
|
|
if parseErr != nil {
|
|
err = fmt.Errorf("解析第%d字节失败: %v", i+1, parseErr)
|
|
return
|
|
}
|
|
mac[i] = byte(value)
|
|
}
|
|
|
|
// 提取productid(第一个字节 + 第二个字节)
|
|
productid = (uint16(mac[0]) << 8) | uint16(mac[1])
|
|
|
|
// 提取批次数据(第三个字节)
|
|
batchNumber = byte(mac[2])
|
|
|
|
// 提取生产编号(第四个字节 + 第五个字节 + 第六个字节)
|
|
serial = (uint32(mac[3]) << 16) | (uint32(mac[4]) << 8) | uint32(mac[5])
|
|
|
|
return
|
|
}
|
|
|
|
// ConvertRelativeDaysToDate 将相对天数转换回实际日期
|
|
func ConvertRelativeDaysToDate(relativeDays uint16) uint32 {
|
|
baseDate := uint32(20250101)
|
|
baseYear := baseDate / 10000
|
|
baseMonth := (baseDate % 10000) / 100
|
|
baseDay := baseDate % 100
|
|
|
|
// 简化计算:假设每月30天
|
|
totalDays := baseYear*365 + baseMonth*30 + baseDay + uint32(relativeDays)
|
|
|
|
// 转换回年月日格式(简化版本)
|
|
year := totalDays / 365
|
|
remainingDays := totalDays % 365
|
|
month := remainingDays / 30
|
|
day := remainingDays % 30
|
|
|
|
if month == 0 {
|
|
month = 1
|
|
}
|
|
if day == 0 {
|
|
day = 1
|
|
}
|
|
|
|
return year*10000 + month*100 + day
|
|
}
|
|
|
|
// formatToHex 将整数转换为指定长度的十六进制字符串
|
|
func formatToHex(num, length int) string {
|
|
if num < 0 {
|
|
num = 0 // 处理负数情况
|
|
}
|
|
|
|
// 使用标准库转换为十六进制
|
|
hexStr := fmt.Sprintf("%x", num)
|
|
|
|
// 补零至指定长度
|
|
hexStr = strings.ToUpper(fmt.Sprintf("%0*s", length, hexStr))
|
|
|
|
// 截取指定长度(处理溢出情况)
|
|
if len(hexStr) > length {
|
|
hexStr = hexStr[len(hexStr)-length:]
|
|
}
|
|
|
|
return hexStr
|
|
}
|
|
|