首页 文章 精选 留言 我的

精选列表

搜索[AI驱动],共10000篇文章
优秀的个人博客,低调大师

加速项目管理效率,Gitee PPM 驱动软件工厂的智能化转型

作者:Gitee DevSecOps 产品经理 李颖萍 在高速发展的软件开发时代,企业如何高效管理多个项目、协调团队合作、优化资源配置,已成为推动技术进步的关键。尤其是在多任务、多项目并行的复杂环境下,Gitee 项目组合管理(PPM)作为一款智能化工具,正成为软件工厂的重要推动力量。 软件工厂:标准化与自动化的未来 传统开发模式中,企业依赖多个独立工具支撑不同的开发任务和项目。随着软件工厂理念的提出,开发模式发生根本性转变:软件工厂强调的是一个完整的生产体系,由「标准化流程 + 自动化执行 + 可复用构件」构成的生产线。 在多项目并行背景下,如何通过高效的项目组合管理优化资源分配、提升执行效率,已成为企业面临的重要挑战。Gitee PPM 正是在此背景下应运而生,以全新的项目管理方式,推动软件工厂智能化转型。 Gitee PPM:项目管理的智能调度与透明协作 Gitee PPM 通过智能调度与跨团队协作机制,为软件工厂提供有力支撑。无论资源调度还是多项目并行推进,Gitee PPM 都能高效保障项目执行: 实时任务进度跟踪:Gitee PPM 提供实时任务进度跟踪功能。通过清晰的进度条和状态标识,团队可随时掌握当前项目状态,管理者也能及时识别需要重点关注和调整的环节。 精准资源调配:用户可清晰查看每个项目的资源使用情况。系统根据需求智能调度资源,避免浪费与冲突,提升整体研发效率。 风险预警与问题追踪:借助任务进度和风险管理模块,Gitee PPM 能及时识别潜在风险并发出预警,帮助项目经理在问题发生前做出调整,确保按时交付。 全生命周期管理:从项目立项到交付,Gitee PPM 贯穿始终 Gitee PPM 的亮点之一是全生命周期管理能力。从立项、排期、执行、监控,到最终交付,Gitee PPM 涵盖项目管理的每一环节,确保每个项目都能够高效且有序地推进。 项目立项与资源规划:提供标准化立项流程,并通过资源规划功能助力前期准备。人员配置、时间安排、预算分配均可系统化管理。 动态调整与进度追踪:在项目推进过程中,Gitee PPM 提供灵活的进度追踪与调整功能,通过直观的进度条与任务分配,管理者可以轻松地进行任务调度和进度优化。 协作与透明化:Gitee PPM 不仅支持项目经理和团队成员之间的沟通,还通过任务审批和资源访问权限的透明化管理,确保每个环节都能得到准确、及时的反馈。团队成员可以实时查看项目状态,随时参与决策,提升工作效率。 未来趋势:智能化与自动化助力软件工厂的跨越式发展 随着人工智能和自动化技术的迅速发展,未来的 PPM 更加注重智能与自动化融合。Gitee PPM 将持续深化智能分析与预测能力,提前识别风险并提供决策支持。 无缝集成 DevSecOps 流程:Gitee PPM 将与 DevSecOps 深度融合,支持 CI/CD 等敏捷方法,提升软件交付速度与质量。 全生命周期智能管理:随着项目规模与复杂性增长,Gitee PPM 将扩展管理范围,覆盖研发、测试、交付和运维等各环节的智能化管理。 Gitee PPM,赋能软件工厂的未来 在软件工厂的智能化转型过程中,Gitee PPM 无疑是其中最为关键的一环。通过智能化的项目管理,精准的资源调配,全面的风险控制以及高效的跨部门协作,Gitee PPM 正在为软件开发行业带来革命性的变化。 未来,Gitee PPM 将继续不断创新,推动软件工厂向更高效、更智能、更安全的方向发展,成为企业实现研发目标、提升交付质量、加速业务增长的核心竞争力。 Gitee DevSecOps 的现代化研发生态 Gitee DevSecOps 是一站式国产化研发与交付平台,集成了代码托管(Code)、项目协作(Team)、持续集成(CI)、持续部署(CD)、代码安全(Scan)、数据洞察(Insight)等多项能力,致力于打造具备全生命周期管控能力的现代软件工厂。 https://gitee.cn/factory 平台设计充分考虑关键领域行业对安全性、可控性、合规性的极高要求,具备以下核心特征: 国产化适配与私有化部署能力:全面兼容国产操作系统与基础设施,支持灵活部署于内网环境,保障数据主权; 全流程 DevSecOps 管控体系:代码从提交、审核、构建、扫描、部署到发布全流程可视、可追溯、安全可控; 模块化产品结构:各能力模块(如 Code、Team、Repo、Pipe、Scan、Insight等)可灵活组合、渐进集成,适配多样化团队与流程要求; 深度可观测与度量体系:内置研发效能度量与数据洞察引擎,支撑管理者宏观掌控项目态势与交付健康度。 在多个国家级重大项目与关键领域单位落地实践中,Gitee DevSecOps 已成为构建「自主、可控、高效、安全」的软件工程体系的重要基石。

优秀的个人博客,低调大师

带着问题读 TiDB 源码:Power BI Desktop 以 MySQL 驱动连接 TiDB 报错

​常有人说,阅读源码是每个优秀开发工程师的必经之路,但是在面对像类似 TiDB 这样复杂的系统时,源码阅读是一个非常庞大的工程。而对一些 TiDB User 来说,从自己日常遇到的问题出发,反过来阅读源码就是一个不错的切入点,因此我们策划了《带着问题读源码》系列文章。 本文为该系列的第二篇,从一个 Power BI Desktop 在 TiDB 上表现异常的问题为例,介绍从问题的发现、定位,到通过开源社区提 issue、写 PR 解决问题的流程,从代码实现的角度来做 trouble shooting,希望能够帮助大家更好地了解 TiDB 源码。 首先我们重现一下失败的场景(TiDB 5.1.1 on MacOS),建一个简单的只有一个字段的表: CREATE TABLE test(name VARCHAR(1) PRIMARY KEY); MySQL 上可以 TiDB 上就不可以,报错 DataSource.Error: An error happened while reading data from the provider: 'Failed to enable constraints. One or more rows contain values violating non-null, unique, or foreign-key constraints.' Details: DataSourceKind=MySql DataSourcePath=localhost:4000;test 看 general log TiDB 上最后一条跑的 SQL 是: select COLUMN_NAME, ORDINAL_POSITION, IS_NULLABLE, DATA_TYPE, case when NUMERIC_PRECISION is null then null when DATA_TYPE in ('FLOAT', 'DOUBLE') then 2 else 10 end AS NUMERIC_PRECISION_RADIX, NUMERIC_PRECISION, NUMERIC_SCALE, CHARACTER_MAXIMUM_LENGTH, COLUMN_DEFAULT, COLUMN_COMMENT AS DESCRIPTION, COLUMN_TYPE from INFORMATION_SCHEMA.COLUMNS where table_schema = 'test' and table_name = 'test'; 我们用 tiup 启动一个 TiDB 集群,使用 tiup client 执行该命令,tiup client 也会报错: error: mysql: sql: Scan error on column index 4, name "NUMERIC_PRECISION_RADIX": converting NULL to int64 is unsupported 那我们的注意力就集中在解决这条语句的问题,我们先看 tiup client 上报的这个错意味着什么。tiup client 使用的是 golang xo/usql 库,但是在 xo/usql 库中,我们并不能找到对应的报错信息,grep converting 关键字返回极有限且无关的内容。我们再看 xo/usql 的 mysql driver,其中又引用到了 go-sql-driver/mysql,下载它的代码并 grep converting,只返回了 changelog 中的一条信息,大概率报错的地方也不在这个库中。浏览一下 go-sql-driver/mysql 中的代码,发现它依赖于 database/sql,那我们看看 database/sql 的内容。database/sql 是 golang 的标准库,所以我们需要下载 golang 的源码。在 golang 的 database 目录中 grep converting,很快就找到了与报错信息相符的内容: go/src/database/sql/convert.go case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: if src == nil { return fmt.Errorf("converting NULL to %s is unsupported", dv.Kind()) } s := asString(src) i64, err := strconv.ParseInt(s, 10, dv.Type().Bits()) if err != nil { err = strconvErr(err) return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err) } dv.SetInt(i64) return nil 我们再追踪这个片段,看这里的类型是如何来的,最终我们会回到 go-sql-driver/mysql 中: mysql/fields.go case fieldTypeLongLong: if mf.flags&flagNotNULL != 0 { if mf.flags&flagUnsigned != 0 { return scanTypeUint64 } return scanTypeInt64 } return scanTypeNullInt 这部分的代码是在解析语句返回体中的 column definition,转换成 golang 中的类型。我们可以使用 mysql --host 127.0.0.1 --port 4000 -u root --column-type-info 连上后查看有问题的 SQL 返回的 column metadata: MySQL Field 5: `NUMERIC_PRECISION_RADIX` Catalog: `def` Database: `` Table: `` Org_table: `` Type: LONGLONG Collation: binary (63) Length: 3 Max_length: 0 Decimals: 0 Flags: BINARY NUM TiDB Field 5: `NUMERIC_PRECISION_RADIX` Catalog: `def` Database: `` Table: `` Org_table: `` Type: LONGLONG Collation: binary (63) Length: 2 Max_length: 0 Decimals: 0 Flags: NOT_NULL BINARY NUM 可以很明显的看到,tiup client 报错信息中的 NUMERIC_PRECISION_RADIX 字段的 column definition 在 TiDB 上有明显的问题,该字段在 TiDB 的返回体中被标记为了 NOT_NULL,很明显这是不合理的,因为该字段显然可以是 NULL,MySQL 的返回值也体现了这一点。所以 xo/usql 在处理返回体的时候报错了。到了这里,我们已经发现了 client 端为什么会报错,下面我们就需要去寻找 TiDB 为什么会返回一个错误的 column definition。 通过 TiDB Dev Guide 我们可以知道 TiDB 中一条 DQL 语句的大体执行过程,我们从入口的 server/conn.go#clientConn.Run 往下看去,一路经过 server/conn.go#clientConn.dispatch、server/conn.go#clientConn.handleQuery、server/conn.go#clientConn.handleStmt、server/driver_tidb.go#TiDBContext.ExecuteStmt、session/session.go#session.ExecuteStmt、executor/compiler.go#Compiler.Compile、planner/optimize.go#Optimize、planner/optimize.go#optimize、planner/core/planbuilder.go#PlanBuilder.Build、planner/core/logical_plan_builder.go#PlanBuilder.buildSelect,在 buildSelect 中,我们可以看到 TiDB planner 对查询语句进行的一系列处理,然后我们就可以走到 planner/core/expression_rewriter.go#PlanBuilder.rewriteWithPreprocess 和 planner/core/expression_rewriter.go#PlanBuilder.rewriteExprNode,在 rewriteExprNode 中,会把有问题的字段 NUMERIC_PRECISION_RADIX 进行解析,最终这条 CASE 表达式的解析会在 expression/builtin_control.go#caseWhenFunctionClass.getFunction 中,我们终于走到了计算 CASE 表达式返回的 column definition 的地方(这依赖于遍历 compiler 解析出的 AST): for i := 1; i < l; i += 2 { fieldTps = append(fieldTps, args[i].GetType()) decimal = mathutil.Max(decimal, args[i].GetType().Decimal) if args[i].GetType().Flen == -1 { flen = -1 } else if flen != -1 { flen = mathutil.Max(flen, args[i].GetType().Flen) } isBinaryStr = isBinaryStr || types.IsBinaryStr(args[i].GetType()) isBinaryFlag = isBinaryFlag || !types.IsNonBinaryStr(args[i].GetType()) } if l%2 == 1 { fieldTps = append(fieldTps, args[l-1].GetType()) decimal = mathutil.Max(decimal, args[l-1].GetType().Decimal) if args[l-1].GetType().Flen == -1 { flen = -1 } else if flen != -1 { flen = mathutil.Max(flen, args[l-1].GetType().Flen) } isBinaryStr = isBinaryStr || types.IsBinaryStr(args[l-1].GetType()) isBinaryFlag = isBinaryFlag || !types.IsNonBinaryStr(args[l-1].GetType()) } fieldTp := types.AggFieldType(fieldTps) // Here we turn off NotNullFlag. Because if all when-clauses are false, // the result of case-when expr is NULL. types.SetTypeFlag(&fieldTp.Flag, mysql.NotNullFlag, false) tp := fieldTp.EvalType() if tp == types.ETInt { decimal = 0 } fieldTp.Decimal, fieldTp.Flen = decimal, flen if fieldTp.EvalType().IsStringKind() && !isBinaryStr { fieldTp.Charset, fieldTp.Collate = DeriveCollationFromExprs(ctx, args...) if fieldTp.Charset == charset.CharsetBin && fieldTp.Collate == charset.CollationBin { // When args are Json and Numerical type(eg. Int), the fieldTp is String. // Both their charset/collation is binary, but the String need a default charset/collation. fieldTp.Charset, fieldTp.Collate = charset.GetDefaultCharsetAndCollate() } } else { fieldTp.Charset, fieldTp.Collate = charset.CharsetBin, charset.CollationBin } if isBinaryFlag { fieldTp.Flag |= mysql.BinaryFlag } // Set retType to BINARY(0) if all arguments are of type NULL. if fieldTp.Tp == mysql.TypeNull { fieldTp.Flen, fieldTp.Decimal = 0, types.UnspecifiedLength types.SetBinChsClnFlag(fieldTp) } 查看如上计算 column definition flag 的代码我们可以发现,无论 CASE 表达式的情况是怎么样的,NOT_NULL 标记位都一定会被设置成 false,所以问题不出现在这里!这个时候我们只能沿着上面的代码路径往回看,看看上面生成的 column definition 在后续有没有被修改。终于在 server/conn.go#clientConn.handleStmt 中,发现它调用了 server/conn.go#clientConn.writeResultSet,然后又陆续调用了server/conn.go#clientConn.writeChunks、server/conn.go#clientConn.writeColumnInfo、server/column.go#ColumnInfo.Dump 和 server/column.go#dumpFlag,在 dumpFlag 中,之前生成的 column definition flag 被修改了: func dumpFlag(tp byte, flag uint16) uint16 { switch tp { case mysql.TypeSet: return flag | uint16(mysql.SetFlag) case mysql.TypeEnum: return flag | uint16(mysql.EnumFlag) default: if mysql.HasBinaryFlag(uint(flag)) { return flag | uint16(mysql.NotNullFlag) } return flag } } 终于,我们找到了 TiDB 返回错误的 column definition 的原因!其实这个 bug 在 TiDB 最新版5.2.0中已经被修复了:*: fix some problems related to notNullFlag by wjhuang2016 · Pull Request #27697 · pingcap/tidb。 最后,在上述阅读代码的过程中,我们其实最好能够看到被 TiDB 解析后的 AST 是什么样子的,这样在最后遍历 AST 的过程中,才不至于摸瞎。TiDB dev guide 中有 parser 章节讲解如何调试 parser,parser/quickstart.md at master · pingcap/parser 中也有样例输出生成的 AST,但是简单地输出基本没有任何作用,我们可以使用 davecgh/go-spew 直接输出 parser 生成的 node,这样就能获得一个可被人理解的 tree: package main import ( "fmt" "github.com/pingcap/parser" "github.com/pingcap/parser/ast" _ "github.com/pingcap/parser/test_driver" "github.com/davecgh/go-spew/spew" ) func parse(sql string) (*ast.StmtNode, error) { p := parser.New() stmtNodes, _, err := p.Parse(sql, "", "") if err != nil { return nil, err } return &stmtNodes[0], nil } func main() { spew.Config.Indent = " " astNode, err := parse("SELECT a, b FROM t") if err != nil { fmt.Printf("parse error: %v\n", err.Error()) return } fmt.Printf("%s\n", spew.Sdump(*astNode)) } (*ast.SelectStmt)(0x140001dac30)({ dmlNode: (ast.dmlNode) { stmtNode: (ast.stmtNode) { node: (ast.node) { text: (string) (len=18) "SELECT a, b FROM t" } } }, resultSetNode: (ast.resultSetNode) { resultFields: ([]*ast.ResultField) <nil> }, SelectStmtOpts: (*ast.SelectStmtOpts)(0x14000115bc0)({ Distinct: (bool) false, SQLBigResult: (bool) false, SQLBufferResult: (bool) false, SQLCache: (bool) true, SQLSmallResult: (bool) false, CalcFoundRows: (bool) false, StraightJoin: (bool) false, Priority: (mysql.PriorityEnum) 0, TableHints: ([]*ast.TableOptimizerHint) <nil> }), Distinct: (bool) false, From: (*ast.TableRefsClause)(0x140001223c0)({ node: (ast.node) { text: (string) "" }, TableRefs: (*ast.Join)(0x14000254100)({ node: (ast.node) { text: (string) "" }, resultSetNode: (ast.resultSetNode) { resultFields: ([]*ast.ResultField) <nil> }, Left: (*ast.TableSource)(0x14000156480)({ node: (ast.node) { text: (string) "" }, Source: (*ast.TableName)(0x1400013a370)({ node: (ast.node) { text: (string) "" }, resultSetNode: (ast.resultSetNode) { resultFields: ([]*ast.ResultField) <nil> }, Schema: (model.CIStr) , Name: (model.CIStr) t, DBInfo: (*model.DBInfo)(<nil>), TableInfo: (*model.TableInfo)(<nil>), IndexHints: ([]*ast.IndexHint) <nil>, PartitionNames: ([]model.CIStr) { } }), AsName: (model.CIStr) }), Right: (ast.ResultSetNode) <nil>, Tp: (ast.JoinType) 0, On: (*ast.OnCondition)(<nil>), Using: ([]*ast.ColumnName) <nil>, NaturalJoin: (bool) false, StraightJoin: (bool) false }) }), Where: (ast.ExprNode) <nil>, Fields: (*ast.FieldList)(0x14000115bf0)({ node: (ast.node) { text: (string) "" }, Fields: ([]*ast.SelectField) (len=2 cap=2) { (*ast.SelectField)(0x140001367e0)({ node: (ast.node) { text: (string) (len=1) "a" }, Offset: (int) 7, WildCard: (*ast.WildCardField)(<nil>), Expr: (*ast.ColumnNameExpr)(0x14000254000)({ exprNode: (ast.exprNode) { node: (ast.node) { text: (string) "" }, Type: (types.FieldType) unspecified, flag: (uint64) 8 }, Name: (*ast.ColumnName)(0x1400017dc70)(a), Refer: (*ast.ResultField)(<nil>) }), AsName: (model.CIStr) , Auxiliary: (bool) false }), (*ast.SelectField)(0x14000136840)({ node: (ast.node) { text: (string) (len=1) "b" }, Offset: (int) 10, WildCard: (*ast.WildCardField)(<nil>), Expr: (*ast.ColumnNameExpr)(0x14000254080)({ exprNode: (ast.exprNode) { node: (ast.node) { text: (string) "" }, Type: (types.FieldType) unspecified, flag: (uint64) 8 }, Name: (*ast.ColumnName)(0x1400017dce0)(b), Refer: (*ast.ResultField)(<nil>) }), AsName: (model.CIStr) , Auxiliary: (bool) false }) } }), GroupBy: (*ast.GroupByClause)(<nil>), Having: (*ast.HavingClause)(<nil>), WindowSpecs: ([]ast.WindowSpec) <nil>, OrderBy: (*ast.OrderByClause)(<nil>), Limit: (*ast.Limit)(<nil>), LockTp: (ast.SelectLockType) none, TableHints: ([]*ast.TableOptimizerHint) <nil>, IsAfterUnionDistinct: (bool) false, IsInBraces: (bool) false, QueryBlockOffset: (int) 0, SelectIntoOpt: (*ast.SelectIntoOption)(<nil>) })

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册