影刀RPA 测试环境与生产环境的配置切换方案
作者:林焱
什么情况用这个
你辛辛苦苦在本地调试好的流程,部署到服务器上后一堆问题:测试时用的测试网站URL还在代码里、数据库连的是本地的、邮件发到了测试邮箱而不是客户的。一个个改过来吧——文件路径、数据库连接、API地址、通知接收人——几十个地方要改,改完还怕漏了。
职业的做法是:流程设计时就区分测试和生产两套环境,通过一个开关切换。改一个变量,所有配置自动切过去。这篇文章给你一套环境管理方案,从最简单的变量开关到完整的配置文件体系。
怎么做
第一步:最简方案——一个环境变量控制一切
在影刀变量面板中定义一个全局变量ENV:
变量名: ENV 值: dev (开发/测试环境 → dev,生产环境 → prod)然后在每一个需要区分环境的Python节点中,根据ENV的值选择不同的配置:
# 配置字典——所有环境相关配置都集中在这里CONFIG={"dev":{"base_url":"https://test.example.com","api_url":"https://test-api.example.com","db_host":"192.168.1.100","db_name":"test_db","db_user":"dev_user","output_dir":"D:/test_output","notify_email":"dev@company.com","log_level":"DEBUG",},"prod":{"base_url":"https://www.example.com","api_url":"https://api.example.com","db_host":"10.0.1.50","db_name":"prod_db","db_user":"prod_user","output_dir":"D:/prod_output","notify_email":"ops@company.com","log_level":"WARNING",}}# 从影刀变量面板读取 ENV(在输入变量中声明)# 然后选择对应环境的配置env=ENVifENVinCONFIGelse"dev"# 默认开发环境env_config=CONFIG[env]print(f"当前环境:{env}")print(f"API:{env_config['api_url']}")第二步:把配置抽到JSON文件中
拼多多店群自动化报活动上架!
变量面板里的ENV好管,但CONFIG字典写在代码里不方便修改。抽到JSON文件:
文件结构:
D:/rpa_config/ ├── config.dev.json # 开发环境 ├── config.test.json # 测试环境 └── config.prod.json # 生产环境config.dev.json:
{"env":"dev","base_url":"https://test.example.com","api":{"base_url":"https://test-api.example.com","timeout":30,"max_retry":3},"database":{"type":"mysql","host":"192.168.1.100","port":3306,"name":"test_db","user":"dev_user"},"paths":{"input":"D:/test_data/input","output":"D:/test_data/output","log":"D:/test_data/logs"},"notification":{"email_to":["dev@company.com"],"wecom_webhook":"https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=test","dingtalk_webhook":""},"features":{"send_notification":false,"backup_before_process":true,"detailed_logging":true}}读取配置的通用函数:
importjsonimportosclassConfigLoader:"""配置加载器"""def__init__(self,config_dir="D:/rpa_config"):self.config_dir=config_dir self.config={}defload(self,env="dev"):"""加载指定环境的配置"""config_file=os.path.join(self.config_dir,f"config.{env}.json")ifnotos.path.exists(config_file):raiseFileNotFoundError(f"配置文件不存在:{config_file}")withopen(config_file,"r",encoding="utf-8")asf:self.config=json.load(f)print(f"已加载配置:{config_file}")print(f" 环境:{self.config.get('env')}")print(f" API: {self.config.get('api', {}).get('base_url')}")returnself.configdefget(self,key_path,default=None):""" 用点号路径获取配置值 如: config.get("api.timeout") → 30 config.get("database.host") → "192.168.1.100" """keys=key_path.split(".")value=self.configforkeyinkeys:ifisinstance(value,dict):value=value.get(key)else:returndefaultreturnvalueifvalueisnotNoneelsedefault# ===== 在影刀流程中使用 =====# 第一个Python节点config=ConfigLoader()config.load(ENV)# ENV从影刀变量读取# 后续节点中到处使用api_url=config.get("api.base_url")db_host=config.get("database.host")output_dir=config.get("paths.output")should_notify=config.get("features.send_notification",False)第三步:敏感信息与普通配置分离
数据库密码、API密钥等敏感信息不能放在JSON文件中(那些文件可能会提交到Git)。单独管理:
// config.prod.json(不含敏感信息){"database":{"host":"10.0.1.50","port":3306,"name":"prod_db","user":"prod_user"// 密码在secrets中},"api":{"base_url":"https://api.example.com",// key在secrets中}}// secrets.prod.json(敏感信息,不提交Git){"database":{"password":"实际密码"},"api":{"api_key":"sk-实际密钥"}}defload_full_config(env):"""加载完整配置(合并公开配置和敏感配置)"""config=ConfigLoader()base_config=config.load(env)# 尝试加载敏感配置secrets_file=os.path.join(config.config_dir,f"secrets.{env}.json")ifos.path.exists(secrets_file):withopen(secrets_file,"r",encoding="utf-8")asf:secrets=json.load(f)# 深度合并(敏感信息覆盖基础配置)deep_merge(base_config,secrets)print(f"已加载敏感配置:{secrets_file}")else:print(f"未找到敏感配置文件:{secrets_file}")returnbase_configdefdeep_merge(base,override):"""深度合并两个字典"""forkey,valueinoverride.items():ifkeyinbaseandisinstance(base[key],dict)andisinstance(value,dict):deep_merge(base[key],value)else:base[key]=value第四步:数据库连接的环境切换
importpymysqldefget_db_connection(config):"""根据配置创建数据库连接"""db_config={"host":config.get("database.host"),"port":config.get("database.port",3306),"user":config.get("database.user"),"password":config.get("database.password"),"database":config.get("database.name"),"charset":"utf8mb4",}# 测试环境可能不需要密码ifnotdb_config["password"]:db_config.pop("password",None)returnpymysql.connect(**db_config)第五步:不同环境的行为差异
不光数据不同,行为也可以不同:
# 根据环境决定是否发送真实通知ifconfig.get("features.send_notification"):send_wecom_alert("流程完成")else:print(f"[{env}] 跳过通知发送(当前环境不发送通知)")# 根据环境决定是否备份原始数据ifconfig.get("features.backup_before_process"):backup_file(source_path)# 根据环境决定日志详细程度log_level=config.get("log_level","INFO")有什么坑
坑一:生产代码中残留测试配置
现象:部署到生产后,URL还在调测试网站的API,生产数据写到了测试数据库。
解决:在配置文件中加一个env字段做自检:
# 启动时验证env_name=config.get("env")ifenv_name=="prod":# 确保几个关键配置不是测试环境的assert"test"notinconfig.get("api.base_url",""),\"生产环境不能使用测试API地址!"assertconfig.get("features.send_notification"),\"生产环境必须开启通知!"坑二:JSON配置文件写了注释导致解析失败
现象:在JSON里加了// 这是注释,加载时报JSON格式错误。
TEMU店群矩阵自动化运营核价报活动
原因:标准JSON不支持注释。
解决:要么用不写注释的JSON,要么改用YAML或TOML格式。但最省事的方法是用字段名做自解释:
{"api_base_url_comment":"API服务器地址,生产环境为 https://api.example.com","api_base_url":"https://api.example.com"}坑三:不同环境文件路径不一致
现象:开发环境路径是D:/project/data/,生产是E:/app/data/,忘了改。
解决:配置中的路径用环境变量或相对路径:
{"paths":{"data_root":"D:/rpa_data","input":"${data_root}/input","output":"${data_root}/output"}}坑四:多台机器共用配置文件
现象:配置文件在所有机器上都是一样的,但每台机器的磁盘分区不同。
解决:配置文件里写相对路径或使用目录引用,机器级别的差异放在环境的系统变量中。
坑五:修改配置后没有立即生效
现象:改了config.json,流程还是用的老配置。
原因:配置是在流程启动时加载一次,长流程运行期间不会重新读取。
解决:每天晚上重新启动一次流程(用系统的计划任务),或者在每个循环开始时重新读取配置。
总结:环境管理不是为了好看,是为了避免"在测试环境跑了三个月没问题,一上生产全崩"的灾难。最低成本的做法:定义一个ENV变量,所有的URL、路径、邮箱都用这个变量来选择。一劳永逸的做法:用JSON配置文件 + ConfigLoader,新项目直接复制这套模板。