金蝶云星空服务插件[python]开发小结

in 普通BLOG
1 评论 阅读量:4083

金蝶云星空 Python 插件开发总结

本文档是金蝶云星空插件开发过程中的实战血泪经验总结,涵盖环境限制、避坑指南和高级调试技巧。

一、平台介绍

金蝶云星空平台(企业版)进行插件开发,利用平台IronPython能力,开发python插件。python在平台中,是一种借壳执行的操作方式,故无法使用任何外部的import动作,只能引用内部的组件和实体对象。目前平台使用的是 IronPython 2.7.x 版本,开发时必需严格使用 Python 2.7 语法及标准库,绝不可运用 Python 3.x 的新特性,以免出现兼容性问题。


二、环境限制与底层"连环坑" (致命报错篇)

金蝶使用的是高度裁剪版的 IronPython 2.7 运行环境,它既不完全是 Python,也不完全是 C#,这导致了大量原生的代码写法在这里会直接崩溃。

1. 终极杀手:"编码 0" 报错

# 错误写法 (极易触发编码0报错)
msg = "当前行号: " + str(row_num)

# 规范写法 (100% 安全)
from System.Text import StringBuilder
sb = StringBuilder()
sb.Append(u"当前行号: ").AppendLine(unicode(row_num))

2. 标准库阉割 (Missing Modules)

3. 引用语法极度脆弱 (Unexpected Token)


三、触发机制与上下文 (触发生效篇)

1. "点击没反应" 的千古难题

# 严谨的按钮监听写法 (统一转大写防呆)
def BarItemClick(e):
    if unicode(e.BarItemKey).upper() == u"PVRH_TBBUTTON_7":
        execute_logic()

def ButtonClick(e):
    if unicode(e.Key).upper() == u"PVRH_TBBUTTON_7":
        execute_logic()

2. 上下文丢失 (Context Error)


四、DynamicObject 数据穿透技巧 (ORM 查表篇)

金蝶的 this.Model.DataObject 取出来的数据是 DynamicObject,它披着 Python 字典的外衣,但本质是 C# 对象。

1. 字典判断的陷阱 (Contains 报错)

# 错误
if row.Contains(u"PVRH_Cust_Entry100037"):

# 正确
if row.DynamicObjectType.Properties.ContainsKey(u"PVRH_Cust_Entry100037"):

2. 辅助资料取值 (Name 属性不存在)

# 稳健的辅助资料取值
dir_val = unicode(dir_obj[u"FDataValue"]) if dir_obj else u"None"

3. 子明细 (Sub-Entry) 的遍历法

sub_key = u"PVRH_Cust_Entry100037"
if row.DynamicObjectType.Properties.ContainsKey(sub_key):
    sub_rows = row[sub_key]  # 拿到集合
    if sub_rows and sub_rows.Count > 0:
        for sub in sub_rows:
            # 拿子行里的基础资料对象
            opt_base = sub[u"F_PVRH_Base1"]
            # 取编码
            o_code = unicode(opt_base[u"Number"]) if opt_base else u""

五、开发技巧

  1. 开发组件引用(clr.AddReference) 和 实体对象导入(from XXX import *) 尽量多、全,不用考虑性能问题,避免从哪里复制过来的代码,因为引用和导入的遗漏导致方法无法使用报错
  2. 区分字段属性的(标识)字段名绑定实体属性,字段预加载事件@OnPreparePropertys 中使用的是(标识),DataEntitys取值中使用的是绑定实体属性
  3. 测试过程中,尽管加上[操作前确认提示]、[操作成功后提示],测试中就会有弹窗反馈,就可以知道有没有走到服务插件代码中;另外需要注意!服务插件重新编辑保存后,单据要关闭重新打开后才生效!
  4. 有部分数据处理,如实在无法理解到基础库实体对象的方法如何使用,可以尝试在python中使用内置函数解决;如一些基础的字符串转换、处理,无需import导入的动作(服务插件中不支持导入)
  5. 实体类型Entity中不存在名为XXX的属性,注意看下属性是归属于当前单据,还是由其他属性引用带入。如果是由其他属性(元素类型为:基础资料)引用,可以将该属性打印或者保存,观察下数据结构再进行获取

    # 以下示例仅供参考
    F_PVRH_BaseProperty_qtr = ("{0}").format(FEItem['F_PVRH_Base2']['Specification']) # 产品编号
    F_PVRH_Creator = ("{0}").format(billObj['F_PVRH_CreatorId']['Name']) # 制单人名称
    F_PVRH_Base = ("{0}").format(billObj['F_PVRH_Base']['Name']) # 客户名称
    
    _name = first_bill_obj['Name'][2052] # 多语言文本示例 取得商品名称 2052=>中文
  6. 写日志到上机操作日志控制台

    log = LogObject()
    log.pkValue = bill_no + " start" # 唯一标志
    log.Description = bill_no + " 推送开始"
    log.OperateName = "推送开始"
    log.ObjectTypeId = this.BusinessInfo.GetForm().Id # 操作的业务对象ID
    log.SubSystemId = this.BusinessInfo.GetForm().SubsysId # 子系统Id
    log.Environment = OperatingEnvironment.BizOperate # 操作员
    LogServiceHelper.WriteLog(this.Context, log)

    二开案例.服务插件.写日志
    Python插件中记录上机操作日志

  7. 调试常用
  8. 表单类插件:this.View.ShowMessage("调试信息")
  9. 服务类插件:raise Exception("调试信息")

六、业务算法设计规范 (防混配与隔离)

1. 界面刷新强制化

this.View.UpdateView(u"F_PVRH_Combo_qtr2")
this.View.UpdateView(u"F_PVRH_Remarks_83g")

七、"核弹级"调试技巧:数据透视法

在金蝶云里,Python 是写在沙箱里的黑盒,无法像本地 IDE 那样断点调试。如果取值不对,千万不要靠猜

1. 属性全量扫描法

当你不知道一个 DynamicObject 里到底有什么字段(比如之前的操作方向),直接写循环暴打出来:

sb.AppendLine(u" [对象属性全扫描]:")
for p in obj.DynamicObjectType.Properties:
    try:
        p_val = unicode(obj[unicode(p.Name)])
        sb.Append(u"  - ").Append(unicode(p.Name)).Append(u" : ").AppendLine(p_val)
    except:
        pass

2. 体检报告输出模式

构建清晰的 [STEP 1] -> 当前值 -> [PASS/FAIL] 日志流水。哪里红了查哪里,这不仅是开发利器,上线后更是业务排障的最佳工具。


八、数据库操作示例

在金蝶云星空中,ExecuteScalarExecuteExecuteDataSetExecuteDynamicObject是四个常用的数据库操作方法,它们的区别如下:

1. ExecuteScalar

2. Execute

3. ExecuteDataSet

4. ExecuteDynamicObject

5. 总结


九、参考代码

# -*- coding: utf-8 -*-
# 表单插件示例:点击按钮执行打包计算

import clr
clr.AddReference('System')
clr.AddReference('mscorlib')

import System
from System.Text import StringBuilder
from Kingdee.BOS.ServiceHelper import DBServiceHelper

# ================= 业务配置 =================
TARGET_BUTTON_KEY = u"PVRH_tbButton_7"
VAL_PACK_OVERALL = u"A"   # 整体包装
VAL_PACK_SPLIT   = u"B"   # 分体包装

# ================= 事件入口 =================
def BarItemClick(e):
    if unicode(e.BarItemKey).upper() == TARGET_BUTTON_KEY.upper():
        execute_pack_logic()

def ButtonClick(e):
    if unicode(e.Key).upper() == TARGET_BUTTON_KEY.upper():
        execute_pack_logic()

# ================= 核心业务逻辑 =================
def execute_pack_logic():
    # 使用 StringBuilder 收集日志(避免编码问题)
    sb = StringBuilder()
    sb.AppendLine(u"### 自动打包计算 ###")

    try:
        # Step 1: 全局校验
        sb.AppendLine(u"\n[Step 1] 全局校验:")
        site_val = unicode(this.Model.GetValue(u"F_PVRH_Combo_re5"))
        site_pass = (site_val == u"1")
        sb.Append(u"  现场满足: ").AppendLine(u"[OK]" if site_pass else u"[FAIL]")

        # Step 2: 逐行处理
        bill_obj = this.Model.DataObject
        entry_rows = bill_obj[u"SaleOrderEntry"]

        for i in range(entry_rows.Count):
            row = entry_rows[i]

            # 默认分体
            this.Model.SetValue(u"F_PVRH_Combo_qtr2", VAL_PACK_SPLIT, i)

            # 业务逻辑处理...

        # Step 3: 刷新界面
        this.View.UpdateView(u"F_PVRH_Combo_qtr2")
        this.View.ShowMessage(sb.ToString())

    except Exception as ex:
        this.View.ShowMessage(u"运行异常: " + unicode(ex))

# ================= 工具函数 =================
def post(url, postData):
    bytes = Encoding.ASCII.GetBytes(postData)
    webRequest = HttpWebRequest.Create(url)
    webRequest.Method = 'POST'
    webRequest.ContentType = 'application/json'
    webRequest.Timeout = 1000 * 60 * 10
    webRequest.ContentLength = bytes.Length

    webRequest.GetRequestStream().Write(bytes, 0, bytes.Length)
    webRequest.GetRequestStream().Flush()
    webRequest.GetRequestStream().Close()
    webResponse = webRequest.GetResponse()
    streamReader = StreamReader(webResponse.GetResponseStream(), Encoding.GetEncoding('utf-8'))
    result = streamReader.ReadToEnd()
    return result

def get(url):
    webRequest = HttpWebRequest.Create(url)
    webRequest.Method = 'GET'
    webResponse = webRequest.GetResponse()
    streamReader = StreamReader(webResponse.GetResponseStream(), Encoding.GetEncoding('utf-8'))
    result = streamReader.ReadToEnd()
    return result

# 中文字符串转换成Unicode编码 避免在 Encoding.ASCII.GetBytes 方法中被转成乱码
def tidyString(s):
    return s.encode('unicode_escape').decode('ascii') if isinstance(s, str) else s

十、参考资料

Responses
  1. name.xx

    cool

    Reply