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 dataimport "io"type DataReader interface {Read(io.Reader, interface{}) ([]interface{}, error)
}
Excel实现
package dataimport ("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.Rowsvar headers []CellHeadervar 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.Columnif 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 []CellHeaderfor _, 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 []CellColumnfor i, cell := range row.Cells {// 忽略 header 所在的第一列if i == 0 {continue}if i >= len(headers) {break}cellValue := getCellValue(cell)column := CellColumn{// headers 从 0 开始,所以这里 -1Header: 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, nilcase 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(), nildefault: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 stringField reflect.Value
}type CellColumn struct {Header CellHeaderValue 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, nilcase 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(), nildefault:return nil, fmt.Errorf("unsupported type: %v", fieldType.Kind())}
}
2.3.单元测试用例
由于go只有main包能使用main函数,为了对我们的工具进行测试,我们可以直接使用类的单元测试。
新建一个文件excel_test.go(必须以_test结尾)
package dataimport ("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 dataimport "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 {@Idprivate int id;/*** 名字*/private String name;/*** 排序*/private int rank;/*** 类型*/@Indexprivate 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) {// 创建 ExcelDataReaderreader := NewExcelDataReader(true)// 读取 Excel 文件records, err := reader.Read("mall.xlsx", &Mall{})if err != nil {fmt.Println("Failed to read Excel file:", err)return}// 创建 Containercontainer := 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)}// 创建 Containercontainer := 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命名为CommonContainerString 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) {// 创建 ExcelDataReaderreader := 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游戏服务器
相关文章:

go读取excel游戏配置
1.背景 游戏服务器,配置数据一般采用csv/excel来作为载体,这种方式,策划同学配置方便,服务器解析也方便。在jforgame框架里,我们使用以下的excel配置格式。 然后可以非常方便的进行数据检索,例如ÿ…...
特殊类设计
[本节目标] 掌握常见特殊类的设计方式 1.请设计一个类,不能被拷贝 拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。 C98 将拷贝构…...

图像去雾数据集的下载和预处理操作
前言 目前,因为要做对比实验,收集了一下去雾数据集,并且建立了一个数据集的预处理工程。 这是以前我写的一个小仓库,我决定还是把它用起来,下面将展示下载的路径和数据处理的方法。 下面的代码均可以在此找到。Auo…...

【LeetCode】--- MySQL刷题集合
1.组合两个表(外连接) select p.firstName,p.lastName,a.city,a.state from Person p left join Address a on p.personId a.personId; 以左边表为基准,去连接右边的表。取两表的交集和左表的全集 2.第二高的薪水 (子查询、if…...

基于Python的多元医疗知识图谱构建与应用研究(上)
一、引言 1.1 研究背景与意义 在当今数智化时代,医疗数据呈爆发式增长,如何高效管理和利用这些数据,成为提升医疗服务质量的关键。传统医疗数据管理方式存在数据孤岛、信息整合困难等问题,难以满足现代医疗对精准诊断和个性化治疗的需求。知识图谱作为一种知识表示和管理…...
小哆啦解题记:如何计算除自身以外数组的乘积
小哆啦开始力扣每日一题的第十二天 https://leetcode.cn/problems/product-of-array-except-self/description/ 《小哆啦解题记:如何计算除自身以外数组的乘积》 在一个清晨的阳光下,小哆啦坐在书桌前,思索着一道困扰已久的题目:…...
渐进式图片的实现原理
渐进式图片(Progressive JPEG)的实现原理与传统的基线 JPEG(Baseline JPEG)不同。它通过改变图片的编码和加载方式,使得图片在加载时能够逐步显示从模糊到清晰的图像。 1. 传统基线 JPEG 的加载方式 在传统的基线 JP…...
SQL刷题快速入门(三)
其他章节: SQL刷题快速入门(一) SQL刷题快速入门(二) 承接前两个章节,本系列第三章节主要讲SQL中where和having的作用和区别、 GROUP BY和ORDER BY作用和区别、表与表之间的连接操作(重点&…...

mybatis(19/134)
大致了解了一下工具类,自己手敲了一边,java的封装还是真的省去了很多麻烦,封装成一个工具类就可以不用写很多重复的步骤,一个工厂对应一个数据库一个environment就好了。 mybatis中调用sql中的delete占位符里面需要有字符…...

sqlmap 自动注入 -01
1: 先看一下sqlmap 的help: 在kali-linux 系统里面,可以sqlmap -h看一下: Target: At least one of these options has to be provided to define the target(s) -u URL, --urlURL Target URL (e.g. "Salesforce Platform for Application Development | Sa…...
3.8.Trie树
Trie树 Trie 树,又称字典树或前缀树,是一种用于高效存储和检索字符串数据的数据结构,以下是关于它的详细介绍: 定义与原理 定义:Trie 树是一种树形结构,每个节点可以包含多个子节点,用于存储…...
day 21
进程、线程、协程的区别 进程:操作系统分配资源的最小单位,其中可以包含一个或者多个线程,进程之间是独立的,可以通过进程间通信机制(管道,消息队列,共享内存,信号量,信…...
基于模板方法模式-消息队列发送
基于模板方法模式-消息队列发送 消息队列广泛应用于现代分布式系统中,作为解耦、异步处理和流量控制的重要工具。在消息队列的使用中,发送消息是常见的操作。不同的消息队列可能有不同的实现方式,例如,RabbitMQ、Kafka、RocketMQ…...

俄语画外音的特点
随着全球媒体消费的增加,语音服务呈指数级增长。作为视听翻译和本地化的一个关键方面,画外音在确保来自不同语言和文化背景的观众能够以一种真实和可访问的方式参与内容方面发挥着重要作用。说到俄语,画外音有其独特的特点、挑战和复杂性&…...

PyTorch使用教程(10)-torchinfo.summary网络结构可视化详细说明
1、基本介绍 torchinfo是一个为PyTorch用户量身定做的开源工具,其核心功能之一是summary函数。这个函数旨在简化模型的开发与调试流程,让模型架构一目了然。通过torchinfo的summary函数,用户可以快速获取模型的详细结构和统计信息࿰…...

亚博microros小车-原生ubuntu支持系列:5-姿态检测
MediaPipe 介绍参见:亚博microros小车-原生ubuntu支持系列:4-手部检测-CSDN博客 本篇继续迁移姿态检测。 一 背景知识 以下来自亚博官网 MediaPipe Pose是⼀个⽤于⾼保真⾝体姿势跟踪的ML解决⽅案,利⽤BlazePose研究,从RGB视频…...

C语言之高校学生信息快速查询系统的实现
🌟 嗨,我是LucianaiB! 🌍 总有人间一两风,填我十万八千梦。 🚀 路漫漫其修远兮,吾将上下而求索。 C语言之高校学生信息快速查询系统的实现 目录 任务陈述与分析 问题陈述问题分析 数据结构设…...

WPF基础 | WPF 基础概念全解析:布局、控件与事件
WPF基础 | WPF 基础概念全解析:布局、控件与事件 一、前言二、WPF 布局系统2.1 布局的重要性与基本原理2.2 常见布局面板2.3 布局的测量与排列过程 三、WPF 控件3.1 控件概述与分类3.2 常见控件的属性、方法与事件3.3 自定义控件 四、WPF 事件4.1 路由事件概述4.2 事…...
迷宫1.2
先发一下上次的代码 #include<bits/stdc.h> #include<windows.h> #include <conio.h> using namespace std; char a[1005][1005]{ " ", "################", "# # *#", "# # # #&qu…...

RabbitMQ---应用问题
(一)幂等性介绍 幂等性是本身是数学中的运算性质,他们可以被多次应用,但是不会改变初始应用的结果 1.应用程序的幂等性介绍 包括很多,有数据库幂等性,接口幂等性以及网络通信幂等性等 就比如数据库的sel…...
figma 和蓝湖 有什么区别
以下是 Figma 和蓝湖的详细对比分析: 核心定位区别 维度Figma蓝湖本质全功能云端设计工具设计协作与交付平台核心功能设计原型协作开发交付设计稿交付标注切图协作设计能力✅ 完整矢量设计工具❌ 无设计功能(需导入其他工具文件)适用阶段全流…...
C++.OpenGL (1/64) 创建窗口(Hello Window)
OpenGL 创建窗口(Hello Window) 步骤详解与代码实现 #mermaid-svg-436DlGvysFQogISc {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-436DlGvysFQogISc .error-icon{fill:#552222;}#mermaid-svg-436DlGvysFQogISc…...
C#封装HttpClient:HTTP请求处理最佳实践
C#封装HttpClient:HTTP请求处理最佳实践 在现代的.NET应用程序开发中,与外部服务进行HTTP通信是一项常见需求。HttpClient作为.NET框架中处理HTTP请求的核心组件,为我们提供了强大而灵活的API。然而,直接使用原生的HttpClient可能…...

华为云Flexus+DeepSeek征文|基于华为云Flexus X和DeepSeek-R1打造个人知识库问答系统
目录 前言 1 快速部署:一键搭建Dify平台 1.1 部署流程详解 1.2 初始配置与登录 2 构建专属知识库 2.1 进入知识库模块并创建新库 2.2 选择数据源导入内容 2.3 上传并识别多种文档格式 2.4 文本处理与索引构建 2.5 保存并完成知识库创建 3接入ModelArts S…...
《前端面试题:JS数据类型》
JavaScript 数据类型指南:从基础到高级全解析 一、JavaScript 数据类型概述 JavaScript 作为一门动态类型语言,其数据类型系统是理解这门语言的核心基础。在 ECMAScript 标准中,数据类型分为两大类: 1. 原始类型(Pr…...

智能制造数字孪生全要素交付一张网:智造中枢,孪生领航,共建智造生态共同体
在制造业转型升级的浪潮中,数字孪生技术正成为推动行业变革的核心引擎。从特斯拉通过数字孪生体实现车辆全生命周期优化,到海尔卡奥斯工业互联网平台赋能千行百业,数字孪生技术已从概念验证走向规模化落地。通过构建覆盖全国的交付网络&#…...

SpringBoot手动实现流式输出方案整理以及SSE规范输出详解
背景: 最近做流式输出时,一直使用python实现的,应需求方的要求,需要通过java应用做一次封装并在java侧完成系统鉴权、模型鉴权等功能后才能真正去调用智能体应用,基于此调研java实现流式输出的几种方式,并…...

go语言的锁
本篇文章主要讲锁,主要会涉及go的sync.Mutex和sync.RWMutex。 一.锁的概念和发展 1.1 锁的概念 所谓的加锁和解锁其实就是指一个数据是否被占用了,通过Mutex内的一个状态来表示。 例如,取 0 表示未加锁,1 表示已加锁ÿ…...

第46节:多模态分类(图像+文本)
一、多模态分类概述 多模态分类是指利用来自不同模态(如图像、文本、音频等)的数据进行联合分析和分类的任务。 在当今大数据时代,信息往往以多种形式存在,例如社交媒体上的图片配文字、视频附带字幕、医疗检查中的影像与报告等。单一模态的数据往往只能提供有限的信息,…...
跨平台游戏引擎 Axmol-2.6.1 发布
Axmol 2.6.1 版本是一个以错误修复和功能改进为主的次要LTS长期支持版本 🙏感谢所有贡献者及财务赞助者:scorewarrior、peterkharitonov、duong、thienphuoc、bingsoo、asnagni、paulocoutinhox、DelinWorks 错误修复 修复Android armv7架构崩溃问题&…...