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.
424 lines
11 KiB
424 lines
11 KiB
package api_test
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
"yunyan/comm"
|
|
"yunyan/lego/sys/mysql"
|
|
"yunyan/pb"
|
|
"yunyan/utils/mac"
|
|
)
|
|
|
|
// 生成自定义 MAC 地址(64 位 / 8 字节)
|
|
func generateCustomMAC(devicetype byte, factoryid byte, batchid uint32, serial uint32) (string, error) {
|
|
if serial > 0xFFFFFF {
|
|
return "", fmt.Errorf("serial number exceeds 3-byte limit (max 16777215)")
|
|
}
|
|
|
|
mac := make([]byte, 8)
|
|
|
|
// Byte 0: 设备类型
|
|
mac[0] = devicetype
|
|
|
|
// Byte 1: 厂商 ID
|
|
mac[1] = factoryid
|
|
|
|
// Byte 2~4: 批次 ID(日期压缩或编号)
|
|
mac[2] = byte((batchid >> 16) & 0xFF)
|
|
mac[3] = byte((batchid >> 8) & 0xFF)
|
|
mac[4] = byte(batchid & 0xFF)
|
|
|
|
// Byte 5~7: 序号(3字节)
|
|
mac[5] = byte((serial >> 16) & 0xFF)
|
|
mac[6] = byte((serial >> 8) & 0xFF)
|
|
mac[7] = byte(serial & 0xFF)
|
|
|
|
// 格式化成 MAC 字符串:每个字节2位,共 8 段
|
|
return fmt.Sprintf("%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X",
|
|
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], mac[6], mac[7]), nil
|
|
}
|
|
|
|
// 解析自定义 MAC 地址字符串为结构体数据
|
|
func parseCustomMAC(macStr string) (devicetype, factoryid byte, batchid, serial uint32, err error) {
|
|
// 去掉冒号并转换为字节
|
|
macStr = strings.ReplaceAll(macStr, ":", "")
|
|
data, err := hex.DecodeString(macStr)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if len(data) != 8 {
|
|
err = fmt.Errorf("invalid MAC length: expected 8 bytes, got %d", len(data))
|
|
return
|
|
}
|
|
|
|
// Byte 0: 设备类型
|
|
devicetype = data[0]
|
|
|
|
// Byte 1: 厂商 ID
|
|
factoryid = data[1]
|
|
|
|
// Byte 2~4: 批次 ID
|
|
batchid = uint32(data[2])<<16 | uint32(data[3])<<8 | uint32(data[4])
|
|
|
|
// Byte 5~7: 序号
|
|
serial = uint32(data[5])<<16 | uint32(data[6])<<8 | uint32(data[7])
|
|
|
|
return
|
|
}
|
|
func Test_Sys_Chat(t *testing.T) {
|
|
// str, _ := GetQWeather("上海")
|
|
// fmt.Println(str)
|
|
mac, err := generateCustomMAC(0x01, 0x02, 250624, 123456)
|
|
if err != nil {
|
|
fmt.Println("Error:", err)
|
|
return
|
|
}
|
|
fmt.Println("MAC Address:", mac)
|
|
devtype, factory, batchid, serial, err := parseCustomMAC(mac)
|
|
if err != nil {
|
|
fmt.Println("Error:", err)
|
|
return
|
|
}
|
|
fmt.Printf("设备类型: 0x%02X\n厂商 ID: 0x%02X\n批次 ID: %d\n序列号: %d\n",
|
|
devtype, factory, batchid, serial)
|
|
}
|
|
func GetQWeather(city string) (value string, err error) {
|
|
var (
|
|
georesp *http.Response
|
|
resp *http.Response
|
|
// body []byte
|
|
)
|
|
|
|
apiKey := "9249776ad0a34213ba4a698931fb16f6"
|
|
// 1. 获取城市ID
|
|
geoUrl := fmt.Sprintf("https://geoapi.qweather.com/v2/city/lookup?location=%s&key=%s", city, apiKey)
|
|
georesp, err = http.Get(geoUrl)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer georesp.Body.Close()
|
|
|
|
var geoData map[string]interface{}
|
|
json.NewDecoder(georesp.Body).Decode(&geoData)
|
|
cityID := geoData["location"].([]interface{})[0].(map[string]interface{})["id"].(string)
|
|
fmt.Printf("获取城市id:%s\n", cityID)
|
|
// 2. 获取天气数据
|
|
// https://api.qweather.com/v7/weather/now?location=101010100&&key=9249776ad0a34213ba4a698931fb16f6
|
|
weatherUrl := fmt.Sprintf("https://api.qweather.com/v7/weather/now?location=%s&key=%s", cityID, apiKey)
|
|
if resp, err = http.Get(weatherUrl); err != nil {
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
// body, err = io.ReadAll(resp.Body)
|
|
var weatherData map[string]interface{}
|
|
json.NewDecoder(resp.Body).Decode(&weatherData)
|
|
|
|
temp := weatherData["now"].(map[string]interface{})["temp"].(string) // 温度
|
|
text := weatherData["now"].(map[string]interface{})["text"].(string) // 天气状况
|
|
// return string(body), nil
|
|
value = fmt.Sprintf("%s: %s℃, %s", city, temp, text)
|
|
return
|
|
}
|
|
|
|
func Test_TavilyApi(t *testing.T) {
|
|
// API配置
|
|
apiURL := "https://api.tavily.com/search"
|
|
apiKey := "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" // 替换为你的实际API密钥
|
|
|
|
// 创建请求体
|
|
requestBody := struct {
|
|
Query string `json:"query"`
|
|
}{
|
|
Query: "今天武汉的天气", // 你可以修改查询内容
|
|
}
|
|
|
|
// 序列化请求体
|
|
jsonData, err := json.Marshal(requestBody)
|
|
if err != nil {
|
|
fmt.Printf("Error marshaling JSON: %v\n", err)
|
|
return
|
|
}
|
|
|
|
// 创建HTTP请求
|
|
req, err := http.NewRequest("POST", apiURL, bytes.NewBuffer(jsonData))
|
|
if err != nil {
|
|
fmt.Printf("Error creating request: %v\n", err)
|
|
return
|
|
}
|
|
|
|
// 设置请求头
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Authorization", "Bearer "+apiKey)
|
|
|
|
// 发送请求
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
fmt.Printf("Error making request: %v\n", err)
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// 读取响应体
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
fmt.Printf("Error reading response body: %v\n", err)
|
|
return
|
|
}
|
|
|
|
// 处理响应
|
|
fmt.Printf("Status Code: %d\n", resp.StatusCode)
|
|
fmt.Printf("Response Body:\n%s\n", body)
|
|
}
|
|
|
|
// 测试api返回效率
|
|
func Test_API_AI(t *testing.T) {
|
|
// 测试参数
|
|
apiURL := "http://ideapsound.com/ai/funcchat" // 替换为你的实际地址
|
|
testMessage := &AIChatReq{
|
|
Msg: "今天的天气如何",
|
|
Contexts: []string{
|
|
"我在武汉",
|
|
},
|
|
}
|
|
|
|
// 运行测试
|
|
if err := testFuncChat(apiURL, testMessage); err != nil {
|
|
fmt.Printf("测试失败: %v\n", err)
|
|
}
|
|
}
|
|
|
|
type AIChatReq struct {
|
|
Msg string `json:"msg"`
|
|
Contexts []string `json:"contexts"`
|
|
}
|
|
|
|
type SSEEvent struct {
|
|
Event string
|
|
Data string
|
|
}
|
|
|
|
func testFuncChat(url string, req *AIChatReq) error {
|
|
// 准备请求体
|
|
reqBody, err := json.Marshal(req)
|
|
if err != nil {
|
|
return fmt.Errorf("编码请求体失败: %w", err)
|
|
}
|
|
|
|
// 创建HTTP请求
|
|
httpReq, err := http.NewRequest("POST", url, bytes.NewBuffer(reqBody))
|
|
if err != nil {
|
|
return fmt.Errorf("创建请求失败: %w", err)
|
|
}
|
|
httpReq.Header.Set("Accept", "text/event-stream")
|
|
httpReq.Header.Set("Content-Type", "application/json")
|
|
|
|
// 记录开始时间
|
|
startTime := time.Now()
|
|
var firstResponseTime time.Duration
|
|
|
|
// 发送请求
|
|
client := &http.Client{}
|
|
resp, err := client.Do(httpReq)
|
|
if err != nil {
|
|
return fmt.Errorf("请求失败: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("非200状态码: %d", resp.StatusCode)
|
|
}
|
|
|
|
// 读取SSE流
|
|
buf := make([]byte, 4096)
|
|
gotFirstResponse := false
|
|
totalBytes := 0
|
|
eventCount := 0
|
|
|
|
for {
|
|
n, err := resp.Body.Read(buf)
|
|
if n > 0 {
|
|
totalBytes += n
|
|
if !gotFirstResponse {
|
|
firstResponseTime = time.Since(startTime)
|
|
gotFirstResponse = true
|
|
fmt.Printf("收到第一个响应时间: %v\n", firstResponseTime)
|
|
}
|
|
|
|
// 简单解析SSE事件
|
|
events := parseSSE(buf[:n])
|
|
eventCount += len(events)
|
|
for _, event := range events {
|
|
fmt.Printf("事件[%s]: %s\n", event.Event, event.Data)
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
return fmt.Errorf("读取响应失败: %w", err)
|
|
}
|
|
}
|
|
|
|
// 输出统计信息
|
|
totalTime := time.Since(startTime)
|
|
fmt.Println("\n测试结果:")
|
|
fmt.Printf("第一个响应时间: %v\n", firstResponseTime)
|
|
fmt.Printf("总传输时间: %v\n", totalTime)
|
|
fmt.Printf("接收事件数量: %d\n", eventCount)
|
|
fmt.Printf("总数据量: %d bytes\n", totalBytes)
|
|
fmt.Printf("平均传输速率: %.2f bytes/ms\n", float64(totalBytes)/totalTime.Seconds()/1000)
|
|
|
|
return nil
|
|
}
|
|
|
|
// 简单SSE解析器
|
|
func parseSSE(data []byte) []SSEEvent {
|
|
var events []SSEEvent
|
|
var currentEvent SSEEvent
|
|
|
|
lines := bytes.Split(data, []byte("\n"))
|
|
for _, line := range lines {
|
|
if len(line) == 0 {
|
|
continue
|
|
}
|
|
|
|
parts := bytes.SplitN(line, []byte(":"), 2)
|
|
if len(parts) < 2 {
|
|
continue
|
|
}
|
|
|
|
field := string(parts[0])
|
|
value := string(parts[1])
|
|
if len(value) > 0 && value[0] == ' ' {
|
|
value = value[1:]
|
|
}
|
|
|
|
switch field {
|
|
case "event":
|
|
currentEvent.Event = value
|
|
case "data":
|
|
currentEvent.Data = value
|
|
events = append(events, currentEvent)
|
|
currentEvent = SSEEvent{}
|
|
}
|
|
}
|
|
|
|
return events
|
|
}
|
|
|
|
// 混淆函数(修复移位错误)
|
|
func obfuscate(numStr string, seed int) string {
|
|
// 限制 seed 范围为 1 到 31
|
|
seed = seed % 31
|
|
if seed == 0 {
|
|
seed = 1 // 避免移位 0 位
|
|
}
|
|
|
|
// 将字符串转为数字
|
|
num, _ := strconv.Atoi(numStr)
|
|
|
|
// 使用 uint32 确保无符号移位
|
|
num32 := uint32(num)
|
|
obfNum := int((num32<<seed | num32>>(32-seed)) ^ uint32(seed))
|
|
|
|
// 转为字符串并确保长度一致
|
|
result := fmt.Sprintf("%0*d", len(numStr), obfNum%int(pow10(len(numStr))))
|
|
return result
|
|
}
|
|
|
|
// 计算10的n次方
|
|
func pow10(n int) int {
|
|
result := 1
|
|
for i := 0; i < n; i++ {
|
|
result *= 10
|
|
}
|
|
return result
|
|
}
|
|
|
|
func Test_GenerateMac(t *testing.T) {
|
|
// 测试生成设备 MAC 地址(productid + 产品级序号)
|
|
productid := uint16(0x0302)
|
|
serial := uint32(1000000)
|
|
|
|
code := mac.Generate(productid, serial)
|
|
fmt.Println("生成的MAC地址:", code)
|
|
|
|
// 测试解析 MAC 地址
|
|
parsedProductid, parsedSerial, err := mac.Parse(code)
|
|
if err != nil {
|
|
t.Errorf("解析MAC地址失败: %v", err)
|
|
}
|
|
|
|
// 验证解析结果是否与原始输入一致
|
|
if parsedProductid != productid {
|
|
t.Errorf("解析的productid不匹配: 期望 0x%04X, 实际 0x%04X", productid, parsedProductid)
|
|
}
|
|
if parsedSerial != serial {
|
|
t.Errorf("解析的serial不匹配: 期望 %d, 实际 %d", serial, parsedSerial)
|
|
}
|
|
|
|
fmt.Printf("解析结果: productid=0x%04X, serial=%d\n", parsedProductid, parsedSerial)
|
|
}
|
|
|
|
// Test_DB 连真实库建表/查数据做一次性排查,默认跳过。
|
|
//
|
|
// DSN 从环境变量取,未设置即跳过。
|
|
// ⚠️ 原先写死的是云雁上海机的地址**和明文库密码**——那台机 2026-09-13 已从本仓库移除
|
|
// (本机没有它的钥匙),写死的凭据留在 git 里只是风险,跑起来也只会连不上然后静默 return。
|
|
//
|
|
// API_TEST_DSN='root:pwd@tcp(host:3306)/admin?charset=utf8mb4&parseTime=True&loc=Local' \
|
|
// go test -v -run Test_DB ./modules/api/
|
|
func Test_DB(t *testing.T) {
|
|
dsn := os.Getenv("API_TEST_DSN")
|
|
if dsn == "" {
|
|
t.Skip("未设置 API_TEST_DSN,跳过")
|
|
}
|
|
if sys, err := mysql.NewSys(
|
|
mysql.SetMySQLDsn(dsn),
|
|
); err != nil {
|
|
fmt.Printf("err:%v", err)
|
|
return
|
|
} else {
|
|
// if err = sys.CreateTable(comm.TableLicense, &pb.DBAuthCode{}); err != nil {
|
|
// fmt.Printf("创建表失败: %v", err)
|
|
// return
|
|
// }
|
|
if err = sys.CreateTable(comm.TableBrand, &pb.DBBrand{}); err != nil {
|
|
fmt.Printf("创建表失败: %v", err)
|
|
} else {
|
|
//设置brand表的主键从0xA001开始
|
|
sys.Exec(fmt.Sprintf("ALTER TABLE %s AUTO_INCREMENT = %d", comm.TableBrand, 0xA001))
|
|
}
|
|
if err = sys.CreateTable(comm.TableProductionBatch, &pb.DBProductionBatch{}); err != nil {
|
|
fmt.Printf("创建表失败: %v", err)
|
|
}
|
|
if err = sys.CreateTable(comm.TableProduct, &pb.DBProduct{}); err != nil {
|
|
fmt.Printf("创建表失败: %v", err)
|
|
} else {
|
|
//设置product表的主键从1000开始
|
|
sys.Exec(fmt.Sprintf("ALTER TABLE %s AUTO_INCREMENT = %d", comm.TableProduct, 0xB001))
|
|
}
|
|
if err = sys.CreateTable(comm.TableProductVersion, &pb.DBProductVersion{}); err != nil {
|
|
fmt.Printf("创建表失败: %v", err)
|
|
}
|
|
|
|
// if err = sys.CreateTable(comm.TableLicense, &pb.DBAuthCode{}); err != nil {
|
|
// fmt.Printf("创建表失败: %v", err)
|
|
// }
|
|
if err = sys.CreateTable(comm.TableGoods, &pb.DBGoods{}); err != nil {
|
|
fmt.Printf("创建表失败: %v", err)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
|