go读取excel游戏配置

1.背景

游戏服务器,配置数据一般采用csv/excel来作为载体,这种方式,策划同学配置方便,服务器解析也方便。在jforgame框架里,我们使用以下的excel配置格式。

然后可以非常方便的进行数据检索,例如:

本文使用go实现类似的功能。 

2.读取excel

2.1.使用github.com/tealeg/xlsx 库

github.com/tealeg/xlsx 是一个流行的 Go 语言库,用于读取和写入 Excel 文件。

定义数据读取接口,既可以选择excel格式,也可以拓展成csv等格式。

package data

import "io"

type DataReader interface {
	Read(io.Reader, interface{}) ([]interface{}, error)
}

Excel实现

package data

import (
	"encoding/json"
	"fmt"
	"reflect"
	"strconv"
	"strings"

	"github.com/tealeg/xlsx"
)

type ExcelDataReader struct {
	ignoreUnknownFields bool
}

func NewExcelDataReader(ignoreUnknownFields bool) *ExcelDataReader {
	return &ExcelDataReader{
		ignoreUnknownFields: ignoreUnknownFields,
	}
}

func (r *ExcelDataReader) Read(filePath string, clazz interface{}) ([]interface{}, error) {
	// 使用 xlsx.OpenFile 打开 Excel 文件
	xlFile, err := xlsx.OpenFile(filePath)
	if err != nil {
		return nil, fmt.Errorf("failed to open Excel file: %v", err)
	}

	sheet := xlFile.Sheets[0]
	rows := sheet.Rows

	var headers []CellHeader
	var records [][]CellColumn

	// 遍历每一行
	for _, row := range rows {
		firstCell := getCellValue(row.Cells[0])
		if firstCell == "HEADER" {
			headers, err = r.readHeader(clazz, row.Cells)
			if err != nil {
				return nil, err
			}
			continue
		}

		if len(headers) == 0 {
			continue
		}

		record := r.readExcelRow(headers, row)
		records = append(records, record)

		if firstCell == "end" {
			break
		}
	}

	return r.readRecords(clazz, records)
}

func (r *ExcelDataReader) readRecords(clazz interface{}, rows [][]CellColumn) ([]interface{}, error) {
	var records []interface{}
	clazzType := reflect.TypeOf(clazz).Elem()

	for _, row := range rows {
		obj := reflect.New(clazzType).Elem()

		for _, column := range row {
			colName := column.Header.Column
			if colName == "" {
				continue
			}

			// 根据 Tag 查找字段
			field, err := findFieldByTag(obj, colName)
			if err != nil {
				if !r.ignoreUnknownFields {
					return nil, err
				}
				continue
			}

			fieldVal, err := convertValue(column.Value, field.Type())
			if err != nil {
				return nil, err
			}

			field.Set(reflect.ValueOf(fieldVal))
		}

		records = append(records, obj.Interface())
	}

	return records, nil
}

func (r *ExcelDataReader) readHeader(clazz interface{}, cells []*xlsx.Cell) ([]CellHeader, error) {
	var headers []CellHeader

	for _, cell := range cells {
		cellValue := getCellValue(cell)
		if cellValue == "HEADER" {
			continue
		}

		header := CellHeader{
			Column: cellValue,
		}

		headers = append(headers, header)
	}

	return headers, nil
}

func getCellValue(cell *xlsx.Cell) string {
	if cell == nil {
		return ""
	}
	return cell.String()
}

func (r *ExcelDataReader) readExcelRow(headers []CellHeader, row *xlsx.Row) []CellColumn {
	var columns []CellColumn

	for i, cell := range row.Cells {
		// 忽略 header 所在的第一列
		if i == 0 {
			continue
		}
		if i >= len(headers) {
			break
		}

		cellValue := getCellValue(cell)
		column := CellColumn{
			// headers 从 0 开始,所以这里 -1
			Header: headers[i-1],
			Value:  cellValue,
		}
		columns = append(columns, column)
	}

	return columns
}

func convertValue(value string, fieldType reflect.Type) (interface{}, error) {
	switch fieldType.Kind() {
	case reflect.String:
		return value, nil
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return strconv.ParseInt(value, 10, 64)
	case reflect.Float32, reflect.Float64:
		return strconv.ParseFloat(value, 64)
	case reflect.Bool:
		return strconv.ParseBool(value)
	case reflect.Slice, reflect.Struct:
		// 处理嵌套的 JSON 对象
		fieldVal := reflect.New(fieldType).Interface()
		if err := json.Unmarshal([]byte(value), &fieldVal); err != nil {
			return nil, fmt.Errorf("failed to unmarshal JSON: %v", err)
		}
		return reflect.ValueOf(fieldVal).Elem().Interface(), nil
	default:
		return nil, fmt.Errorf("unsupported type: %v", fieldType.Kind())
	}
}

// 根据 Tag 查找字段
func findFieldByTag(obj reflect.Value, tagValue string) (reflect.Value, error) {
	objType := obj.Type()
	for i := 0; i < objType.NumField(); i++ {
		field := objType.Field(i)
		tag := field.Tag.Get("excel")         // 获取 Tag 值
		if strings.EqualFold(tag, tagValue) { // 忽略大小写匹配
			return obj.Field(i), nil
		}
	}
	return reflect.Value{}, fmt.Errorf("field with tag %s not found", tagValue)
}

type CellHeader struct {
	Column string
	Field  reflect.Value
}

type CellColumn struct {
	Header CellHeader
	Value  string
}

2.2.主要技术点

这里有几个需要注意的点

2.2.1.go结构体变量与excel字段分离

go使用首字母大写来标识一个变量是否包外可见,如果直接使用go的反射api,需要将excel的字段定义成大写,两者强绑定在一起,不方便。为了支持代码与配置命名的分离,可以使用go的tag定义,通过把excel的字段名称,写在struct的tag注释。有点类似于java的注解。

type Item struct {
	Id      int64  `json:"id" excel:"id"`
	Name    string `json:"name" excel:"name"`
	Quality int64  `json:"quality" excel:"quality"`
	Tips    string `json:"tips" excel:"tips"`
	Icon    string `json:"icon" excel:"icon"`
}

代码片段

// 根据 Tag 查找字段
func findFieldByTag(obj reflect.Value, tagValue string) (reflect.Value, error) {
	objType := obj.Type()
	for i := 0; i < objType.NumField(); i++ {
		field := objType.Field(i)
		tag := field.Tag.Get("excel")         // 获取 Tag 值
		if strings.EqualFold(tag, tagValue) { // 忽略大小写匹配
			return obj.Field(i), nil
		}
	}
	return reflect.Value{}, fmt.Errorf("field with tag %s not found", tagValue)
}

2.2.2.exce支持嵌套结构

程序员很喜欢配置直接使用json格式,这样代码具有很高的拓展性,当策划改配置,只要不添加新类型,都可以无需程序介入。(其实大部分策划很讨厌json格式,配置容易出错,而且excel的自动公式无法很智能地工作

例如下面的配置

结构体定义 

type RewardDef struct {
	Type  string `json:"type" excel:"type"`
	Value string `json:"value" excel:"value"`
}

type ConsumeDef struct {
	Type  string `json:"type" excel:"type"`
	Value string `json:"value" excel:"value"`
}

type Mall struct {
	Id       int64        `json:"id" excel:"id"`
	Type     int64        `json:"type" excel:"type"`
	Name     string       `json:"name" excel:"name"`
	Rewards  []RewardDef  `json:"rewards" excel:"rewards"`
	Consumes []ConsumeDef `json:"consumes" excel:"consumes"`
}

主要代码


func convertValue(value string, fieldType reflect.Type) (interface{}, error) {
	switch fieldType.Kind() {
	case reflect.String:
		return value, nil
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return strconv.ParseInt(value, 10, 64)
	case reflect.Float32, reflect.Float64:
		return strconv.ParseFloat(value, 64)
	case reflect.Bool:
		return strconv.ParseBool(value)
	case reflect.Slice, reflect.Struct:
		// 处理嵌套的 JSON 对象
		fieldVal := reflect.New(fieldType).Interface()
		if err := json.Unmarshal([]byte(value), &fieldVal); err != nil {
			return nil, fmt.Errorf("failed to unmarshal JSON: %v", err)
		}
		return reflect.ValueOf(fieldVal).Elem().Interface(), nil
	default:
		return nil, fmt.Errorf("unsupported type: %v", fieldType.Kind())
	}
}

2.3.单元测试用例

由于go只有main包能使用main函数,为了对我们的工具进行测试,我们可以直接使用类的单元测试。

新建一个文件excel_test.go(必须以_test结尾)

package data

import (
	"fmt"
	"io/github/gforgame/logger"
	"testing"
)

func TestExcelReader(t *testing.T) {
	// 创建 ExcelDataReader 实例
	reader := NewExcelDataReader(true)

	type RewardDef struct {
		Type  string `json:"type" excel:"type"`
		Value string `json:"value" excel:"value"`
	}

	type ConsumeDef struct {
		Type  string `json:"type" excel:"type"`
		Value string `json:"value" excel:"value"`
	}

	type Name struct {
		Id       int64        `json:"id" excel:"id"`
		Name     string       `json:"type" excel:"name"`
		Rewards  []RewardDef  `json:"rewards" excel:"rewards"`
		Consumes []ConsumeDef `json:"consumes" excel:"consumes"`
	}

	// 读取 Excel 文件
	result, err := reader.Read("mall.xlsx", &Name{})
	if err != nil {
		logger.Error(fmt.Errorf("session.Send: %v", err))
	}

	// 打印结果
	for _, item := range result {
		fmt.Printf("%+v\n", item)
	}
}

3.数据载体

3.1.数据容器定义

读取excel文件,得到的是一个记录数组,我们还需要进一步进行封装,方便业务代码使用。

所以我们还需要把这批数据塞入到一个容器里,并且容器应该提供至少以下API。

// GetRecord 根据 ID 获取单个记录
func (c *Container[K, V]) GetRecord(id K) (V, bool) {

}

// GetAllRecords 获取所有记录
func (c *Container[K, V]) GetAllRecords() []V {

}

// GetRecordsBy 根据索引名称和索引值获取记录
func (c *Container[K, V]) GetRecordsBy(name string, index interface{}) []V {

}

该容器必须支持泛型,适配不同的表定义。代码如下:

package data

import "fmt"

// Container 是一个通用的数据容器,支持按 ID 查询、按索引查询和查询所有记录
type Container[K comparable, V any] struct {
	data        map[K]V        // 存储 ID 到记录的映射
	indexMapper map[string][]V // 存储索引到记录的映射
}

// NewContainer 创建一个新的 Container 实例
func NewContainer[K comparable, V any]() *Container[K, V] {
	return &Container[K, V]{
		data:        make(map[K]V),
		indexMapper: make(map[string][]V),
	}
}

// Inject 将数据注入容器,并构建索引
func (c *Container[K, V]) Inject(records []V, getIdFunc func(V) K, indexFuncs map[string]func(V) interface{}) {
	for _, record := range records {
		id := getIdFunc(record)
		c.data[id] = record

		// 构建索引
		for name, indexFunc := range indexFuncs {
			indexValue := indexFunc(record)
			key := indexKey(name, indexValue)
			c.indexMapper[key] = append(c.indexMapper[key], record)
		}
	}
}

// GetRecord 根据 ID 获取单个记录
func (c *Container[K, V]) GetRecord(id K) (V, bool) {
	record, exists := c.data[id]
	return record, exists
}

// GetAllRecords 获取所有记录
func (c *Container[K, V]) GetAllRecords() []V {
	records := make([]V, 0, len(c.data))
	for _, record := range c.data {
		records = append(records, record)
	}
	return records
}

// GetRecordsBy 根据索引名称和索引值获取记录
func (c *Container[K, V]) GetRecordsBy(name string, index interface{}) []V {
	key := indexKey(name, index)
	return c.indexMapper[key]
}

// indexKey 生成索引键
func indexKey(name string, index interface{}) string {
	return fmt.Sprintf("%s@%v", name, index)
}

对于java版本的游戏服务器框架,配置表定义格式如下:

/**
 * 成就表
 */
@Setter
@Getter
@DataTable(name = "achievement")
public class AchievementData {

    @Id
    private int id;
    /**
     * 名字
     */
    private String name;
    /**
     * 排序
     */
    private int rank;

    /**
     * 类型
     */
    @Index
    private int type;
    /**
     * 条件,每个类型自行定义配置结构
     */
    private String target;

}

通过@Id注解定义主键,通过@Index注解定义索引。程序业务代码示例:

// 查询单条记录
AchievementData achievementData = GameContext.dataManager.queryById(AchievementData.class, 1);
// 查询指定索引的所有记录
List<AchievementData> records = GameContext.dataManager.queryByIndex(AchievementData.class, "type", type);

由于go目前不支持注解,无法通过注解让程序自动识别哪一个字段为主键,所以对于每一个容器,需要定义一个函数,手动标识应该取哪一个字段。

	// 定义 ID 获取函数和索引函数
	getIdFunc := func(record Mall) int64 {
		return record.Id
	}

 按索引取记录的逻辑也是同样的道理。

	// 将记录注入容器
	nameRecords := make([]Mall, len(records))
	for i, record := range records {
		nameRecords[i] = record.(Mall)
	}

单元测试代码


func TestDataContainer(t *testing.T) {

	// 创建 ExcelDataReader
	reader := NewExcelDataReader(true)

	// 读取 Excel 文件
	records, err := reader.Read("mall.xlsx", &Mall{})
	if err != nil {
		fmt.Println("Failed to read Excel file:", err)
		return
	}

	// 创建 Container
	container := NewContainer[int64, Mall]()

	// 定义 ID 获取函数和索引函数
	getIdFunc := func(record Mall) int64 {
		return record.Id
	}
	indexFuncs := map[string]func(Mall) interface{}{
		"type": func(record Mall) interface{} {
			return record.Type
		},
	}

	// 将记录注入容器
	nameRecords := make([]Mall, len(records))
	for i, record := range records {
		nameRecords[i] = record.(Mall)
	}
	container.Inject(nameRecords, getIdFunc, indexFuncs)

	// 查询记录
	fmt.Println("All records:", container.GetAllRecords())
	target, _ := container.GetRecord(1)
	fmt.Println("Record with ID 1:", target)
	fmt.Println("Records with type 2:", container.GetRecordsBy("type", 2))
}

3.2.适配不同的表配置

从上面的代码可以看出,对于一份excel配置,每次都要复制一段非常相似的代码,无疑非常繁琐。所以我们对以上的代码进一步封装。

首先,定义各种表的元信息(java可通过注解定义)

type TableMeta struct {
	TableName  string            // 表名
	IDField    string            // ID 字段名
	IndexFuncs map[string]string // 索引字段名 -> 索引名称
	RecordType reflect.Type      // 记录类型
}

将excel配置注入容器

func ProcessTable(reader *ExcelDataReader, filePath string, config TableMeta) (*Container[int64, interface{}], error) {
	// 读取 Excel 文件
	records, err := reader.Read(filePath, reflect.New(config.RecordType).Interface())
	if err != nil {
		return nil, fmt.Errorf("failed to read table %s: %v", config.TableName, err)
	}

	// 创建 Container
	container := NewContainer[int64, interface{}]()

	// 定义 ID 获取函数
	getIdFunc := func(record interface{}) int64 {
		val := reflect.ValueOf(record)
		// 如果 record 是指针,则调用 Elem() 获取实际值
		if val.Kind() == reflect.Ptr {
			val = val.Elem()
		}
		field := val.FieldByName(config.IDField)
		return field.Int()
	}

	// 定义索引函数
	indexFuncs := make(map[string]func(interface{}) interface{})
	if config.IndexFuncs != nil {
		for indexName, fieldName := range config.IndexFuncs {
			indexFuncs[indexName] = func(record interface{}) interface{} {
				val := reflect.ValueOf(record)
				// 如果 record 是指针,则调用 Elem() 获取实际值
				if val.Kind() == reflect.Ptr {
					val = val.Elem()
				}
				field := val.FieldByName(fieldName)
				return field.Interface()
			}
		}
	}

	// 将记录注入容器
	container.Inject(records, getIdFunc, indexFuncs)

	return container, nil
}

在jforgame的版本实现,利用java的类扫描,可以非常方便把所有配置容器一次性扫描并注册,如下:

    public void init() {
        if (!StringUtils.isEmpty(properties.getContainerScanPath())) {
            Set<Class<?>> containers = ClassScanner.listAllSubclasses(properties.getContainerScanPath(), Container.class);
            containers.forEach(c -> {
                // container命名必须以配置文件名+Container,例如配置表为common.csv,则对应的Container命名为CommonContainer
                String name = c.getSimpleName().replace("Container", "").toLowerCase();
                containerDefinitions.put(name, (Class<? extends Container>) c);
            });
        }
        Set<Class<?>> classSet = ClassScanner.listClassesWithAnnotation(properties.getTableScanPath(), DataTable.class);
        classSet.forEach(this::registerContainer);
    }

go目前不支持类扫描这种元编程,我们只能通过手动注册。

	// 定义表配置
	tableConfigs := []TableMeta{
		// 商城表
		{
			TableName:  "mall",
			IDField:    "Id",
			IndexFuncs: map[string]string{"type": "Type"},
			RecordType: reflect.TypeOf(Mall{}),
		},
		// 道具表
		{
			TableName:  "item",
			IDField:    "Id",
			RecordType: reflect.TypeOf(Item{}),
		},
	}

3.3.单元测试用例


func TestMultiDataContainer(t *testing.T) {
	// 创建 ExcelDataReader
	reader := NewExcelDataReader(true)

	// 定义表配置
	tableConfigs := []TableMeta{
		// 商城表
		{
			TableName:  "mall",
			IDField:    "Id",
			IndexFuncs: map[string]string{"type": "Type"},
			RecordType: reflect.TypeOf(Mall{}),
		},
		// 道具表
		{
			TableName:  "item",
			IDField:    "Id",
			RecordType: reflect.TypeOf(Item{}),
		},
	}

	// 处理每张表
	containers := make(map[string]*Container[int64, interface{}])
	for _, config := range tableConfigs {
		container, err := ProcessTable(reader, config.TableName+".xlsx", config)
		if err != nil {
			fmt.Printf("Failed to process table %s: %v\n", config.TableName, err)
			continue
		}
		containers[config.TableName] = container
	}

	// 查询商城记录
	mallContainer := containers["mall"]
	fmt.Println("All records in Mall table:", mallContainer.GetAllRecords())
	target, _ := mallContainer.GetRecord(1)
	fmt.Println("Record with ID 1:", target)
	fmt.Println("Records with type 2 in Mall table:", mallContainer.GetRecordsBy("type", 2))

	// 查询商城记录
	itemContainer := containers["item"]
	fmt.Println("All records in Mall table:", itemContainer.GetAllRecords())
	target2, _ := itemContainer.GetRecord(1)
	fmt.Println("Record with ID 1:", target2)
}

 完整代码请移步:

--> go游戏服务器

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/957193.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

热点营销新视角:品牌如何巧妙“跟风”不踩雷?

在当今信息爆炸的时代&#xff0c;热点事件如同流星划过夜空&#xff0c;转瞬即逝&#xff0c;却总能吸引无数眼球。而软文营销作为品牌推广的重要手段之一&#xff0c;如何巧妙“跟风”热点&#xff0c;既能借势提升品牌曝光度&#xff0c;又能避免陷入雷区&#xff0c;成为众…

Trimble三维激光扫描-地下公共设施维护的新途径【沪敖3D】

三维激光扫描技术生成了复杂隧道网络的高度详细的三维模型 项目背景 纽约州北部的地下通道网络已有100年历史&#xff0c;其中包含供暖系统、电线和其他公用设施&#xff0c;现在已经开始显露出老化迹象。由于安全原因&#xff0c;第三方的进入受到限制&#xff0c;在没有现成纸…

TDengine 做 Apache SuperSet 数据源

‌Apache Superset‌ 是一个现代的企业级商业智能&#xff08;BI&#xff09;Web 应用程序&#xff0c;主要用于数据探索和可视化。它由 Apache 软件基金会支持&#xff0c;是一个开源项目&#xff0c;它拥有活跃的社区和丰富的生态系统。Apache Superset 提供了直观的用户界面…

K8S-Pod的环境变量,重启策略,数据持久化,资源限制

1. Pod容器的三种重启策略 注意&#xff1a;k8s所谓的重启容器指的是重新创建容器 cat 07-restartPolicy.yaml apiVersion: v1 kind: Pod metadata:name: nginx-web-imagepullpolicy-always spec:nodeName: k8s233.oldboyedu.com## 当容器异常退出时&#xff0c;始终重启容器r…

03垃圾回收篇(D1_垃圾收集器算法底层导论)

目录 一、为什么我们要去了解垃圾收集和内存分配 二、对象已死&#xff1f; 1. 引用计数算法 2. 可达性分析算法 3. 再谈引用 4. 生存还是死亡 5. 回收方法区 三、垃圾收集算法 1. 简介 2. 分代收集理论 2.1. 弱分代/强分代假说 2.2. 前面两代假说的缺陷 3. 标记-清…

【wiki知识库】08.添加用户登录功能--后端SpringBoot部分

目录 一、今日目标? 二、SpringBoot后端实现 2.1 新增UserLoginParam 2.2 修改UserController 2.3 UserServiceImpl代码 2.4 创建用户上下文工具类 2.5?通过token校验用户&#xff08;重要&#xff09; 2.6 创建WebMvcConfig 2.7 用户权限校验拦截器 一、今日目标 上…

分布式 IO 模块:开启药品罐装产线高效生产新纪元

在药品生产的精密领域&#xff0c;每一个环节都关乎着客户的健康与安全。药品罐装产线作为药品生产的关键环节&#xff0c;其高效运行与精准控制至关重要。明达技术MR30分布式 IO 模块&#xff0c;正以其卓越的性能&#xff0c;成为实现药品罐装产线高效控制&#xff0c;确保产…

【北京迅为】iTOP-4412全能版使用手册-第八十七章 安装Android Studio

iTOP-4412全能版采用四核Cortex-A9&#xff0c;主频为1.4GHz-1.6GHz&#xff0c;配备S5M8767 电源管理&#xff0c;集成USB HUB,选用高品质板对板连接器稳定可靠&#xff0c;大厂生产&#xff0c;做工精良。接口一应俱全&#xff0c;开发更简单,搭载全网通4G、支持WIFI、蓝牙、…

基于海思soc的智能产品开发(视频的后续开发)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 前面我们讨论了camera&#xff0c;也讨论了屏幕驱动&#xff0c;这些都是基础的部分。关键是&#xff0c;我们拿到了这些视频数据之后&#xff0c;…

Linux -- HTTP 请求 与 响应 报文

目录 请求报文&#xff1a; 请求方法 响应报文&#xff1a; 状态码 与 状态码描述 共性 常见的报头 请求报文&#xff1a; 请求方法 方法说明GET获取资源POST传输实体主体PUT传输文件HEAD获得报文首部DELETE删除文件OPTIONS询问支持的方法TRACE追踪路径CONNECT要求用…

HTML<img>标签

例子 如何插入图片&#xff1a; <img src"img_girl.jpg" alt"Girl in a jacket" width"500" height"600"> 下面有更多“自己尝试”的示例。 定义和用法 该<img>标签用于在 HTML 页面中嵌入图像。 从技术上讲&#x…

C++ 面向对象(继承)

三、继承 3.1 继承的概念 基于一个已有的类 去重新定义一个新的类&#xff0c;这种方式我们叫做继承 关于继承的称呼 一个类B 继承来自 类 A 我们一般称呼 A类&#xff1a;父类 基类 B类: 子类 派生类 B继承自A A 派生了B 示例图的语法 class vehicle // 车类 {}class …

mfc操作json示例

首先下载cJSON,加入项目; 构建工程,如果出现, fatal error C1010: unexpected end of file while looking for precompiled head 在cJSON.c文件的头部加入#include "stdafx.h"; 看情况,可能是加到.h或者是.cpp文件的头部,它如果有包含头文件, #include &…

基于微信小程序的模拟考试系统设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

Android10.0定制服务 APK安装或者更新过自动打开APK

需求描述 当用户APK安装|更新 成功之后自动打开APK。 实现思路 编写服务 注册接受安装广播 并判断安装的APK包名是否为客户APK包名,并自动打开。 具体实现 diff --git a/android/build/make/core/tasks/check_boot_jars/package_whitelist.txt b/android/build/make/cor…

linux 下tensorrt的yolov8的前向推理(python 版本)的实现

一、yolov8的python实现的环境搭建 #通过pip安装 pip install ultralytics #通过git克隆GitHub仓库 git clone <https://github.com/ultralytics/ultralytics.git> cd ultralytics #安装依赖 pip install -r requirements.txt #执行推理 yolo predict model./yolov8n.pt …

AI News(1/21/2025):OpenAI 安全疏忽:ChatGPT漏洞引发DDoS风险/OpenAI 代理工具即将发布

1、OpenAI 的安全疏忽&#xff1a;ChatGPT API 漏洞引发DDoS风险 德国安全研究员 Benjamin Flesch 发现了一个严重的安全漏洞&#xff1a;攻击者可以通过向 ChatGPT API 发送一个 HTTP 请求&#xff0c;利用 ChatGPT 的爬虫对目标网站发起 DDoS 攻击。该漏洞源于 OpenAI 在处理…

【数据挖掘实战】 房价预测

本次对kaggle中的入门级数据集&#xff0c;房价回归数据集进行数据挖掘&#xff0c;预测房屋价格。 本人主页&#xff1a;机器学习司猫白 机器学习专栏&#xff1a;机器学习实战 PyTorch入门专栏&#xff1a;PyTorch入门 深度学习实战&#xff1a;深度学习 ok&#xff0c;话不多…

ElasticSearch 学习课程入门(一)

引子 前文已经介绍了windows下如何安装ES&#xff0c;接下来的文章我会边学习边记录。OK&#xff0c;那就让我们开始吧。 一、ES基础操作 1、预备知识 &#xff08;1&#xff09;RESTful REST 指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是 …

QML Binding和Qt.binding详解

一、Binding详解 1、介绍 QML Binding是一种声明式的编程概念&#xff0c;它允许在Qt Quick应用中自动更新属性之间的关系。通过使用Binding&#xff0c;我们可以在属性之间建立依赖关系&#xff0c;当其中一个属性的值发生变化时&#xff0c;绑定的属性会自动更新。QML Bind…