if(response.isSuccess){}else{}的方式,如果我们由于忽略没有设置success字段的值,就可能导致...

探讨在Java中定义布尔类型变量的最佳实践,包括变量命名、基本类型与包装类型的选用,及其在序列化过程中的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在日常开发中,我们会经常要在类中定义布尔类型的变量,比如在给外部系统提供一个RPC接口的时候,我们一般会定义一个字段表示本次请求是否成功的。

关于这个”本次请求是否成功”的字段的定义,其实是有很多种讲究和坑的,稍有不慎就会掉入坑里,在很久之前就遇到过类似的问题,本文就来围绕这个简单分析一下。到底该如何定一个布尔类型的成员变量。

一般情况下,我们可以有以下四种方式来定义一个布尔类型的成员变量:

  1. boolean success
  2. boolean isSuccess
  3. Boolean success
  4. Boolean isSuccess

以上四种定义形式,你日常开发中最常用的是哪种呢?到底哪一种才是正确的使用姿势呢?

通过观察我们可以发现,前两种和后两种的主要区别是变量的类型不同,前者使用的是boolean,后者使用的是Boolean。

另外,第一种和第三种在定义变量的时候,变量命名是success,而另外两种使用isSuccess来命名的。

首先,我们来分析一下,到底应该是用success来命名,还是使用isSuccess更好一点。

success 还是 isSuccess

到底应该是用success还是isSuccess来给变量命名呢?从语义上面来讲,两种命名方式都可以讲的通,并且也都没有歧义。那么还有什么原则可以参考来让我们做选择呢。

在阿里巴巴Java开发手册中关于这一点,有过一个『强制性』规定:

640?wx_fmt=jpeg

那么,为什么会有这样的规定呢?我们看一下POJO中布尔类型变量不同的命名有什么区别吧。

  1. class Model1 {
  2. private Boolean isSuccess;
  3. public void setSuccess(Boolean success) {
  4. isSuccess = success;
  5. }
  6. public Boolean getSuccess() {
  7. return isSuccess;
  8. }
  9. }
  10. class Model2 {
  11. private Boolean success;
  12. public Boolean getSuccess() {
  13. return success;
  14. }
  15. public void setSuccess(Boolean success) {
  16. this.success = success;
  17. }
  18. }
  19. class Model3 {
  20. private boolean isSuccess;
  21. public boolean isSuccess() {
  22. return isSuccess;
  23. }
  24. public void setSuccess(boolean success) {
  25. isSuccess = success;
  26. }
  27. }
  28. class Model4 {
  29. private boolean success;
  30. public boolean isSuccess() {
  31. return success;
  32. }
  33. public void setSuccess(boolean success) {
  34. this.success = success;
  35. }
  36. }

以上代码的setter/getter是使用Intellij IDEA自动生成的,仔细观察以上代码,你会发现以下规律:

  • 基本类型自动生成的getter和setter方法,名称都是isXXX()setXXX()形式的。

  • 包装类型自动生成的getter和setter方法,名称都是getXXX()setXXX()形式的。

既然,我们已经达成一致共识使用基本类型boolean来定义成员变量了,那么我们再来具体看下Model3和Model4中的setter/getter有何区别。

我们可以发现,虽然Model3和Model4中的成员变量的名称不同,一个是success,另外一个是isSuccess,但是他们自动生成的getter和setter方法名称都是isSuccesssetSuccess

Java Bean中关于setter/getter的规范

关于Java Bean中的getter/setter方法的定义其实是有明确的规定的,根据JavaBeans(TM) Specification规定,如果是普通的参数propertyName,要以以下方式定义其setter/getter:

  1. public <PropertyType> get<PropertyName>();
  2. public void set<PropertyName>(<PropertyType> a);

但是,布尔类型的变量propertyName则是单独定义的:

  1. public boolean is<PropertyName>();
  2. public void set<PropertyName>(boolean m);

640?wx_fmt=jpeg

通过对照这份JavaBeans规范,我们发现,在Model4中,变量名为isSuccess,如果严格按照规范定义的话,他的getter方法应该叫isIsSuccess。但是很多IDE都会默认生成为isSuccess。

那这样做会带来什么问题呢。

在一般情况下,其实是没有影响的。但是有一种特殊情况就会有问题,那就是发生序列化的时候。

序列化带来的影响

关于序列化和反序列化请参考Java对象的序列化与反序列化。我们这里拿比较常用的JSON序列化来举例,看看看常用的fastJson、jackson和Gson之间有何区别:

  1. public class BooleanMainTest {
  2. public static void main(String[] args) throws IOException {
  3. //定一个Model3类型
  4. Model3 model3 = new Model3();
  5. model3.setSuccess(true);
  6. //使用fastjson(1.2.16)序列化model3成字符串并输出
  7. System.out.println("Serializable Result With fastjson :" + JSON.toJSONString(model3));
  8. //使用Gson(2.8.5)序列化model3成字符串并输出
  9. Gson gson =new Gson();
  10. System.out.println("Serializable Result With Gson :" +gson.toJson(model3));
  11. //使用jackson(2.9.7)序列化model3成字符串并输出
  12. ObjectMapper om = new ObjectMapper();
  13. System.out.println("Serializable Result With jackson :" +om.writeValueAsString(model3));
  14. }
  15. }
  16. class Model3 implements Serializable {
  17. private static final long serialVersionUID = 1836697963736227954L;
  18. private boolean isSuccess;
  19. public boolean isSuccess() {
  20. return isSuccess;
  21. }
  22. public void setSuccess(boolean success) {
  23. isSuccess = success;
  24. }
  25. public String getHollis(){
  26. return "hollischuang";
  27. }
  28. }

以上代码的Model3中,只有一个成员变量即isSuccess,三个方法,分别是IDE帮我们自动生成的isSuccess和setSuccess,另外一个是作者自己增加的一个符合getter命名规范的方法。

以上代码输出结果:

  1. Serializable Result With fastjson :{"hollis":"hollischuang","success":true}
  2. Serializable Result With Gson :{"isSuccess":true}
  3. Serializable Result With jackson :{"success":true,"hollis":"hollischuang"}

在fastjson和jackson的结果中,原来类中的isSuccess字段被序列化成success,并且其中还包含hollis值。而Gson中只有isSuccess字段。

我们可以得出结论:fastjson和jackson在把对象序列化成json字符串的时候,是通过反射遍历出该类中的所有getter方法,得到getHollis和isSuccess,然后根据JavaBeans规则,他会认为这是两个属性hollis和success的值。直接序列化成json:{“hollis”:”hollischuang”,”success”:true}

但是Gson并不是这么做的,他是通过反射遍历该类中的所有属性,并把其值序列化成json:{“isSuccess”:true}

可以看到,由于不同的序列化工具,在进行序列化的时候使用到的策略是不一样的,所以,对于同一个类的同一个对象的序列化结果可能是不同的。

前面提到的关于对getHollis的序列化只是为了说明fastjson、jackson和Gson之间的序列化策略的不同,我们暂且把他放到一边,我们把他从Model3中删除后,重新执行下以上代码,得到结果:

  1. Serializable Result With fastjson :{"success":true}
  2. Serializable Result With Gson :{"isSuccess":true}
  3. Serializable Result With jackson :{"success":true}

现在,不同的序列化框架得到的json内容并不相同,如果对于同一个对象,我使用fastjson进行序列化,再使用Gson反序列化会发生什么?

  1. public class BooleanMainTest {
  2. public static void main(String[] args) throws IOException {
  3. Model3 model3 = new Model3();
  4. model3.setSuccess(true);
  5. Gson gson =new Gson();
  6. System.out.println(gson.fromJson(JSON.toJSONString(model3),Model3.class));
  7. }
  8. }
  9. class Model3 implements Serializable {
  10. private static final long serialVersionUID = 1836697963736227954L;
  11. private boolean isSuccess;
  12. public boolean isSuccess() {
  13. return isSuccess;
  14. }
  15. public void setSuccess(boolean success) {
  16. isSuccess = success;
  17. }
  18. @Override
  19. public String toString() {
  20. return new StringJoiner(", ", Model3.class.getSimpleName() + "[", "]")
  21. .add("isSuccess=" + isSuccess)
  22. .toString();
  23. }
  24. }

以上代码,输出结果:

Model3[isSuccess=false]

这和我们预期的结果完全相反,原因是因为JSON框架通过扫描所有的getter后发现有一个isSuccess方法,然后根据JavaBeans的规范,解析出变量名为success,把model对象序列化城字符串后内容为{"success":true}

根据{"success":true}这个json串,Gson框架在通过解析后,通过反射寻找Model类中的success属性,但是Model类中只有isSuccess属性,所以,最终反序列化后的Model类的对象中,isSuccess则会使用默认值false。

但是,一旦以上代码发生在生产环境,这绝对是一个致命的问题。

所以,作为开发者,我们应该想办法尽量避免这种问题的发生,对于POJO的设计者来说,只需要做简单的一件事就可以解决这个问题了,那就是把isSuccess改为success。这样,该类里面的成员变量时success,getter方法是isSuccess,这是完全符合JavaBeans规范的。无论哪种序列化框架,执行结果都一样。就从源头避免了这个问题。

引用以下R大关于阿里巴巴Java开发手册这条规定的评价(https://www.zhihu.com/question/55642203):

640?wx_fmt=jpeg

所以,在定义POJO中的布尔类型的变量时,不要使用isSuccess这种形式,而要直接使用success!

Boolean还是boolean?

前面我们介绍完了在success和isSuccess之间如何选择,那么排除错误答案后,备选项还剩下:

  1. boolean success
  2. Boolean success

那么,到底应该是用Boolean还是boolean来给定一个布尔类型的变量呢?

我们知道,boolean是基本数据类型,而Boolean是包装类型。关于基本数据类型和包装类之间的关系和区别请参考一文读懂什么是Java中的自动拆装箱

那么,在定义一个成员变量的时候到底是使用包装类型更好还是使用基本数据类型呢?

我们来看一段简单的代码

  1. /**
  2. * @author Hollis
  3. */
  4. public class BooleanMainTest {
  5. public static void main(String[] args) {
  6. Model model1 = new Model();
  7. System.out.println("default model : " + model1);
  8. }
  9. }
  10. class Model {
  11. /**
  12. * 定一个Boolean类型的success成员变量
  13. */
  14. private Boolean success;
  15. /**
  16. * 定一个boolean类型的failure成员变量
  17. */
  18. private boolean failure;
  19. /**
  20. * 覆盖toString方法,使用Java 8 的StringJoiner
  21. */
  22. @Override
  23. public String toString() {
  24. return new StringJoiner(", ", Model.class.getSimpleName() + "[", "]")
  25. .add("success=" + success)
  26. .add("failure=" + failure)
  27. .toString();
  28. }
  29. }

以上代码输出结果为:

default model : Model[success=null, failure=false]

可以看到,当我们没有设置Model对象的字段的值的时候,Boolean类型的变量会设置默认值为null,而boolean类型的变量会设置默认值为false

即对象的默认值是null,boolean基本数据类型的默认值是false

在阿里巴巴Java开发手册中,对于POJO中如何选择变量的类型也有着一些规定:

640?wx_fmt=jpeg

这里建议我们使用包装类型,原因是什么呢?

举一个扣费的例子,我们做一个扣费系统,扣费时需要从外部的定价系统中读取一个费率的值,我们预期该接口的返回值中会包含一个浮点型的费率字段。当我们取到这个值得时候就使用公式:金额*费率=费用 进行计算,计算结果进行划扣。

如果由于计费系统异常,他可能会返回个默认值,如果这个字段是Double类型的话,该默认值为null,如果该字段是double类型的话,该默认值为0.0。

如果扣费系统对于该费率返回值没做特殊处理的话,拿到null值进行计算会直接报错,阻断程序。拿到0.0可能就直接进行计算,得出接口为0后进行扣费了。这种异常情况就无法被感知。

这种使用包装类型定义变量的方式,通过异常来阻断程序,进而可以被识别到这种线上问题。如果使用基本数据类型的话,系统可能不会报错,进而认为无异常。

以上,就是建议在POJO和RPC的返回值中使用包装类型的原因。

但是关于这一点,作者之前也有过不同的看法:对于布尔类型的变量,我认为可以和其他类型区分开来,作者并不认为使用null进而导致NPE是一种最好的实践。因为布尔类型只有true/false两种值,我们完全可以和外部调用方约定好当返回值为false时的明确语义。

后来,作者单独和《阿里巴巴Java开发手册》、《码出高效》的作者——孤尽 单独1V1(qing) Battle(jiao)了一下。最终达成共识,还是尽量使用包装类型。

但是,作者还是想强调一个我的观点,尽量避免在你的代码中出现不确定的null值。

null何罪之有?

关于null值的使用,我在使用Optional避免NullPointerException、9 Things about Null in Java等文中就介绍过。

null是很模棱两可的,很多时候会导致令人疑惑的的错误,很难去判断返回一个null代表着什么意思。

图灵奖得主Tony Hoare 曾经公开表达过null是一个糟糕的设计。

640?wx_fmt=jpeg

我把 null 引用称为自己的十亿美元错误。它的发明是在1965 年,那时我用一个面向对象语言( ALGOL W )设计了第一个全面的引用类型系统。我的目的是确保所有引用的使用都是绝对安全的,编译器会自动进行检查。但是我未能抵御住诱惑,加入了Null引用,仅仅是因为实现起来非常容易。它导致了数不清的错误、漏洞和系统崩溃,可能在之后 40 年中造成了十亿美元的损失。

当我们在设计一个接口的时候,对于接口的返回值的定义,尽量避免使用Boolean类型来定义。大多数情况下,别人使用我们的接口返回值时可能用if(response.isSuccess){}else{}的方式,如果我们由于忽略没有设置success字段的值,就可能导致NPE(java.lang.NullPointerException),这明显是我们不希望看到的。

所以,当我们要定义一个布尔类型的成员变量时,尽量选择boolean,而不是Boolean。当然,编程中并没有绝对。

private boolean checkBaseMaterial(StringBuilder sb, String goodsId, BaseMaterial mac) { boolean isOk = false; if (mac == null || !BasicConstant.ENABLE_Y.equals(mac.getEnable())) { sb.append(String.format("物品[%s]在XMS中不合法 ", goodsId)); isOk = true; } return isOk; }public void setTransferSysToSave(Transfersys transfersys) { if (null == transfersys.getGoodsType()) { transfersys.setGoodsType(EnumGoodsType.GOODS.getId()); } if (null == transfersys.getGoodsStatus()) { transfersys.setGoodsStatus(EnumGoodsStatus.GOOD.getId()); } transfersys.setIsPos(BasicConstant.ENABLE_Y); // 设置机构信息并校验 Integer serviceType = transfersys.getServiceType(); String xmsOrgId = getXmsOrgId(transfersys); checkOrg(serviceType, xmsOrgId); // 设置物品信息并校验 StringBuilder sb = new StringBuilder(); for (TransfersysItem item : transfersys.getItemList()) { String goodsId = item.getMacno(); Integer quantity = item.getAppQuantity(); BaseMaterial mac = basicService.getMaterial(goodsId); if (checkItemQuantity(sb, goodsId, quantity) || checkBaseMaterial(sb, goodsId, mac)) { continue; } item.setVerifyQuantity(quantity); item.setMacname(mac.getMacname()); } String errMsg = sb.toString(); if (StringUtils.isNotEmpty(errMsg)) { throw new BusinessException(errMsg); } transfersys.setUpdatePerson(transfersys.getCreatePerson()); // 设置状态、标题等信息 Org org = orgService.getById(xmsOrgId); String title = basicService.getOrgName(org.getId()) + "-" + transfersys.getServiceTypeDesc() + "-" + transfersys.getGoodsTypeDesc() + "-" + transfersys.getGoodsStatusDesc() + "-" + DateUtil.date2str(new Date()); transfersys.setTitle(title); transfersys.setStatus(EnumTransfersysStatus.TO_BE_AUDIT.getId()); if (TransfersysUtil.isReceive(transfersys.getServiceType())) { // 收货 transfersys.setToOrgid(org.getId()); transfersys.setToAddress(org.getName()); } else { // 发货 transfersys.setFromOrgid(org.getId()); transfersys.setFromAddress(org.getName()); } if (TransfersysUtil.isMiHome(org.getType())) { // 是否米家 transfersys.setPssXmsBizType(EnumPssXmsBizType.MIHOME); } else { transfersys.setPssXmsBizType(EnumPssXmsBizType.OTHER); } }@Override public Response<TransferSysCreateDTO> create(TransferSysCreateRequest request) { if (request == null || request.getTransfersys() == null) { return Response.getResult(X5GateWayExceptionCode.COMMON_PARAM_ERROR, "req empty"); } log.info("TransferSysGateWayDubboService.create 入参:{}", JsonUtils.writeValueQuite(request)); Transfersys transfersys = request.getTransfersys(); TransferSysCreateDTO transferSysCreateDTO = new TransferSysCreateDTO(); if (StringUtils.isNotBlank(transfersys.getId()) && TransfersysUtil.isWmsServiceType(transfersys.getServiceType())) { ApiLog apiLog = apiLogService.findByParams(ImmutableMap.of("method", transfersys.getId())); if (null == apiLog) { return Response.getResult(X5GateWayExceptionCode.COMMON_PARAM_ERROR, "id not exists"); } String result = StringUtils.EMPTY; try { result = pssProxy.noticeCreatePssBatch(apiLog.getRequest(), apiLog.getIp()); } catch (Exception e) { log.error("noticeCreatePssBatch 创建失败:", e); return Response.getResult(X5GateWayExceptionCode.COMMON_UNEXPECTED_ERROR, e.getMessage()); } transferSysCreateDTO.setBizId(transfersys.getId()); Response<TransferSysCreateDTO> res = Response.getSuccess(transferSysCreateDTO); res.setMessage(result); return res; } //oms单号幂等 try (LockService redisLock = new LockService("mal_oms_lock_" + transfersys.getWfReturnId() + "_" + transfersys.getId())) { if (TransfersysUtil.isOmsServiceType(transfersys.getServiceType()) && StringUtils.isNotEmpty(transfersys.getWfReturnId())) { if (!redisLock.tryLock(TimeUnit.MINUTES.toSeconds(CommonConstants.ONE_INT))) { throw new BusinessException("系统处理中,请稍后处理"); } Transfersys omsTransfersys = transfersysManager.findByWfreturnIdFromMaster(transfersys.getWfReturnId(), transfersys.getServiceType()); if (Objects.nonNull(omsTransfersys)) { transferSysCreateDTO.setBizId(omsTransfersys.getId()); return Response.getSuccess(transferSysCreateDTO); } } Response<String> response = checkSpmToXms(transfersys); if (Boolean.FALSE.equals(Response.isSuccess(response))) { return Response.getResult(X5GateWayExceptionCode.COMMON_PARAM_ERROR, response.getMessage()); } transferSyGateWayManage.setTransferSysToSave(transfersys); transferSysCreateDTO.setBizId(transfersysDubboService.createAndApproval(transfersys)); } catch (Exception e) { log.error("setTransferSysToSave 创建失败:", e); return Response.getResult(X5GateWayExceptionCode.COMMON_UNEXPECTED_ERROR, e.getMessage()); } Response<TransferSysCreateDTO> res = Response.getSuccess(transferSysCreateDTO); res.setMessage("create transfersys success"); return res; }@Override public Response<TransferSysCreateDTO> create(TransferSysCreateRequest request) { if (request == null || request.getTransfersys() == null) { return Response.getResult(X5GateWayExceptionCode.COMMON_PARAM_ERROR, "req empty"); } log.info("TransferSysGateWayDubboService.create 入参:{}", JsonUtils.writeValueQuite(request)); Transfersys transfersys = request.getTransfersys(); TransferSysCreateDTO transferSysCreateDTO = new TransferSysCreateDTO(); if (StringUtils.isNotBlank(transfersys.getId()) && TransfersysUtil.isWmsServiceType(transfersys.getServiceType())) { ApiLog apiLog = apiLogService.findByParams(ImmutableMap.of("method", transfersys.getId())); if (null == apiLog) { return Response.getResult(X5GateWayExceptionCode.COMMON_PARAM_ERROR, "id not exists"); } String result = StringUtils.EMPTY; try { result = pssProxy.noticeCreatePssBatch(apiLog.getRequest(), apiLog.getIp()); } catch (Exception e) { log.error("noticeCreatePssBatch 创建失败:", e); return Response.getResult(X5GateWayExceptionCode.COMMON_UNEXPECTED_ERROR, e.getMessage()); } transferSysCreateDTO.setBizId(transfersys.getId()); Response<TransferSysCreateDTO> res = Response.getSuccess(transferSysCreateDTO); res.setMessage(result); return res; } //oms单号幂等 try (LockService redisLock = new LockService("mal_oms_lock_" + transfersys.getWfReturnId() + "_" + transfersys.getId())) { if (TransfersysUtil.isOmsServiceType(transfersys.getServiceType()) && StringUtils.isNotEmpty(transfersys.getWfReturnId())) { if (!redisLock.tryLock(TimeUnit.MINUTES.toSeconds(CommonConstants.ONE_INT))) { throw new BusinessException("系统处理中,请稍后处理"); } Transfersys omsTransfersys = transfersysManager.findByWfreturnIdFromMaster(transfersys.getWfReturnId(), transfersys.getServiceType()); if (Objects.nonNull(omsTransfersys)) { transferSysCreateDTO.setBizId(omsTransfersys.getId()); return Response.getSuccess(transferSysCreateDTO); } } Response<String> response = checkSpmToXms(transfersys); if (Boolean.FALSE.equals(Response.isSuccess(response))) { return Response.getResult(X5GateWayExceptionCode.COMMON_PARAM_ERROR, response.getMessage()); } transferSyGateWayManage.setTransferSysToSave(transfersys); transferSysCreateDTO.setBizId(transfersysDubboService.createAndApproval(transfersys)); } catch (Exception e) { log.error("setTransferSysToSave 创建失败:", e); return Response.getResult(X5GateWayExceptionCode.COMMON_UNEXPECTED_ERROR, e.getMessage()); } Response<TransferSysCreateDTO> res = Response.getSuccess(transferSysCreateDTO); res.setMessage("create transfersys success"); return res; }@Override public Response<TransferSysCreateDTO> create(TransferSysCreateRequest request) { if (request == null || request.getTransfersys() == null) { return Response.getResult(X5GateWayExceptionCode.COMMON_PARAM_ERROR, "req empty"); } log.info("TransferSysGateWayDubboService.create 入参:{}", JsonUtils.writeValueQuite(request)); Transfersys transfersys = request.getTransfersys(); TransferSysCreateDTO transferSysCreateDTO = new TransferSysCreateDTO(); if (StringUtils.isNotBlank(transfersys.getId()) && TransfersysUtil.isWmsServiceType(transfersys.getServiceType())) { ApiLog apiLog = apiLogService.findByParams(ImmutableMap.of("method", transfersys.getId())); if (null == apiLog) { return Response.getResult(X5GateWayExceptionCode.COMMON_PARAM_ERROR, "id not exists"); } String result = StringUtils.EMPTY; try { result = pssProxy.noticeCreatePssBatch(apiLog.getRequest(), apiLog.getIp()); } catch (Exception e) { log.error("noticeCreatePssBatch 创建失败:", e); return Response.getResult(X5GateWayExceptionCode.COMMON_UNEXPECTED_ERROR, e.getMessage()); } transferSysCreateDTO.setBizId(transfersys.getId()); Response<TransferSysCreateDTO> res = Response.getSuccess(transferSysCreateDTO); res.setMessage(result); return res; } //oms单号幂等 try (LockService redisLock = new LockService("mal_oms_lock_" + transfersys.getWfReturnId() + "_" + transfersys.getId())) { if (TransfersysUtil.isOmsServiceType(transfersys.getServiceType()) && StringUtils.isNotEmpty(transfersys.getWfReturnId())) { if (!redisLock.tryLock(TimeUnit.MINUTES.toSeconds(CommonConstants.ONE_INT))) { throw new BusinessException("系统处理中,请稍后处理"); } Transfersys omsTransfersys = transfersysManager.findByWfreturnIdFromMaster(transfersys.getWfReturnId(), transfersys.getServiceType()); if (Objects.nonNull(omsTransfersys)) { transferSysCreateDTO.setBizId(omsTransfersys.getId()); return Response.getSuccess(transferSysCreateDTO); } } Response<String> response = checkSpmToXms(transfersys); if (Boolean.FALSE.equals(Response.isSuccess(response))) { return Response.getResult(X5GateWayExceptionCode.COMMON_PARAM_ERROR, response.getMessage()); } transferSyGateWayManage.setTransferSysToSave(transfersys); transferSysCreateDTO.setBizId(transfersysDubboService.createAndApproval(transfersys)); } catch (Exception e) { log.error("setTransferSysToSave 创建失败:", e); return Response.getResult(X5GateWayExceptionCode.COMMON_UNEXPECTED_ERROR, e.getMessage()); } Response<TransferSysCreateDTO> res = Response.getSuccess(transferSysCreateDTO); res.setMessage("create transfersys success"); return res; }@Override public Response<TransferSysCreateDTO> create(TransferSysCreateRequest request) { if (request == null || request.getTransfersys() == null) { return Response.getResult(X5GateWayExceptionCode.COMMON_PARAM_ERROR, "req empty"); } log.info("TransferSysGateWayDubboService.create 入参:{}", JsonUtils.writeValueQuite(request)); Transfersys transfersys = request.getTransfersys(); TransferSysCreateDTO transferSysCreateDTO = new TransferSysCreateDTO(); if (StringUtils.isNotBlank(transfersys.getId()) && TransfersysUtil.isWmsServiceType(transfersys.getServiceType())) { ApiLog apiLog = apiLogService.findByParams(ImmutableMap.of("method", transfersys.getId())); if (null == apiLog) { return Response.getResult(X5GateWayExceptionCode.COMMON_PARAM_ERROR, "id not exists"); } String result = StringUtils.EMPTY; try { result = pssProxy.noticeCreatePssBatch(apiLog.getRequest(), apiLog.getIp()); } catch (Exception e) { log.error("noticeCreatePssBatch 创建失败:", e); return Response.getResult(X5GateWayExceptionCode.COMMON_UNEXPECTED_ERROR, e.getMessage()); } transferSysCreateDTO.setBizId(transfersys.getId()); Response<TransferSysCreateDTO> res = Response.getSuccess(transferSysCreateDTO); res.setMessage(result); return res; } //oms单号幂等 try (LockService redisLock = new LockService("mal_oms_lock_" + transfersys.getWfReturnId() + "_" + transfersys.getId())) { if (TransfersysUtil.isOmsServiceType(transfersys.getServiceType()) && StringUtils.isNotEmpty(transfersys.getWfReturnId())) { if (!redisLock.tryLock(TimeUnit.MINUTES.toSeconds(CommonConstants.ONE_INT))) { throw new BusinessException("系统处理中,请稍后处理"); } Transfersys omsTransfersys = transfersysManager.findByWfreturnIdFromMaster(transfersys.getWfReturnId(), transfersys.getServiceType()); if (Objects.nonNull(omsTransfersys)) { transferSysCreateDTO.setBizId(omsTransfersys.getId()); return Response.getSuccess(transferSysCreateDTO); } } Response<String> response = checkSpmToXms(transfersys); if (Boolean.FALSE.equals(Response.isSuccess(response))) { return Response.getResult(X5GateWayExceptionCode.COMMON_PARAM_ERROR, response.getMessage()); } transferSyGateWayManage.setTransferSysToSave(transfersys); transferSysCreateDTO.setBizId(transfersysDubboService.createAndApproval(transfersys)); } catch (Exception e) { log.error("setTransferSysToSave 创建失败:", e); return Response.getResult(X5GateWayExceptionCode.COMMON_UNEXPECTED_ERROR, e.getMessage()); } Response<TransferSysCreateDTO> res = Response.getSuccess(transferSysCreateDTO); res.setMessage("create transfersys success"); return res; } 物料编号无效异常 在 TransferSysGateWayManage.setTransferSysToSave 中,物品 63232 在 XMS 系统中校验不合法(如未注册、状态异常等),这个ERROR日志,这个问题如何解决属于业务错误(预期内错误)
07-24
我现在有两个脚本,login.py和zaixian.py,两个脚本有依赖关系,现在需要你根据zaixian.py的脚本逻辑重新编写一个业务脚本yewu.py,处理接口请求,脚本输出内容把apimessage字段改成apiresponse字段apiresponse用于存储接口的响应数据 login.py的原始脚本内容是: #!/usr/bin/python3 # coding=utf-8 import io import sys import time import requests import json import re import base64 from urllib.parse import urlparse, urljoin, quote import urllib3 import gzip import zlib import brotli import chardet from typing import Optional, Tuple, Dict # 禁用SSL警告 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') SUC_RES = { 'resCode': 200, 'resTime': 0, 'keyword': 'SUCCESS', 'message': [] } FAIL_RES = { 'resCode': 500, 'resTime': 0, 'keyword': 'FAILED', 'message': [] } # 封装解码 class HttpResponseProcessor: def __init__(self, url: str, headers: Optional[Dict] = None): """ 初始化响应处理器 :param url: 请求的URL :param headers: 请求头,默认为None """ self.url = url self.headers = headers or {} self.response = None self.raw_content = None self.text_content = None self.encoding = None self.status_code = None def fetch_response(self): """ 发送HTTP请求并获取响应 :return: None """ try: self.response = requests.get( url=self.url, headers=self.headers, allow_redirects=False, # 禁用自动重定向 stream=True, # 流模式获取原始响应 verify=False ) self.status_code = self.response.status_code self.raw_content = self.response.content except Exception as e: raise Exception(f"请求失败: {str(e)}") def print_response_headers(self): """ 打印响应头信息 :return: None """ if not self.response: raise Exception("尚未获取响应,请先调用 fetch_response()") def decode_content(self) -> str: """ 尝试解码内容为文本 :return: 解码后的文本内容 """ if not self.raw_content: raise Exception("尚未获取原始内容,请先调用 fetch_response()") try: # 检测内容编码 result = chardet.detect(self.raw_content) encoding_detected = result.get('encoding') if result else None # 尝试解码 if encoding_detected: try: self.text_content = self.raw_content.decode(encoding_detected) self.encoding = encoding_detected return self.text_content except UnicodeDecodeError: # 如果检测到的编码解码失败,则尝试其他编码 pass # 尝试常见编码 for encoding in ['utf-8', 'gbk', 'gb2312', 'latin1']: try: self.text_content = self.raw_content.decode(encoding) self.encoding = encoding break except: continue else: # 如果都无法解码,则使用替换错误字符的方式解码 try: self.text_content = self.raw_content.decode('utf-8', errors='replace') self.encoding = 'utf-8' except: self.text_content = "无法解码内容" self.encoding = None return self.text_content except Exception as e: # 将内容保存到文件以便分析 with open('response.bin', 'wb') as f: f.write(self.raw_content) raise Exception(f"内容解码失败: {str(e)}") def process_response(self) -> Tuple[int, Optional[str], Optional[str]]: """ 完整处理响应的便捷方法 :return: (status_code, text_content, encoding) """ self.fetch_response() self.print_response_headers() text = self.decode_content() return self.status_code, text, self.encoding def print_err_result(e): FAIL_RES['error'] = e exit(1) def make_request(url, params=None, data=None, method='get', session=None): try: start = time.time() req_func = session.get if session else requests.get if method.lower() == 'post': req_func = session.post if session else requests.post headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Accept': 'application/json,text/plain,text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'X-Requested-With': 'XMLHttpRequest', 'accept-encoding': 'gzip, deflate, br,zstd' } response = req_func( url, params=params, data=data, verify=False, headers=headers ) res_time = (time.time() - start) * 1000 if response.status_code in [200, 302]: SUC_RES['resTime'] = int(res_time) SUC_RES['message'].append(f"请求 {url} 成功") return response else: FAIL_RES[ 'error'] = f"请求失败,状态码: {response.status_code}, 响应内容: {response.text}, 头信息:{session.headers if session else None}" FAIL_RES['message'].append(f"请求 {url} 失败") return None except Exception as e: print_err_result(f"请求过程中发生错误: {str(e)}") return None def cas_login(username, password) -> Tuple[Optional[str], Optional[dict]]: # 使用会话保持cookies session = requests.Session() token = None try: # 第一步:获取lt令牌 params1 = { 'service': 'https://www.fifedu.com/iplat/ssoservice', 'get-lt': 'true', 'n': str(int(time.time() * 1000)), 'callback': 'jsonpcallback', '_': str(int(time.time() * 1000)) } url1 = "https://cycore.fifedu.com/cas-server/login" response1 = make_request(url1, params=params1, session=session) if not response1: return None, {} # 1. 检查响应是否以jsonpcallback开头 if not response1.text.startswith('jsonpcallback'): raise ValueError("响应格式不符合预期,不是JSONP格式") # 2. 提取括号内的JSON部分 json_str = response1.text[len('jsonpcallback('):-2] # 去掉首尾的jsonpcallback(); # 3. 将字符串解析为字典 try: data = json.loads(json_str) except json.JSONDecodeError: raise ValueError("JSON解析失败,响应内容: " + response1.text) # 4. 提取所需的 lt = data.get('lt', '') execution = data.get('execution', '') if not lt or not execution: raise ValueError("响应中缺少lt或execution字段") # 第二步:提交登录表单 # 注意:密码是base64编码的,但这里我们直接使用传入的密码(原始代码中密码是base64编码的,所以这里我们直接使用) # 实际上,在登录请求中,密码应该是明文还是编码?根据观察,原始代码中密码是base64编码的,但登录表单提交的是原始密码还是编码后的? # 由于我们传入的password已经是base64编码(从get_credentials中获取的),但实际登录接口可能需要明文,所以这里需要先解码? # 但是,在原始代码中,密码是直接以base64字符串形式传入的,而登录接口是否要求base64编码?需要根据实际接口要求。 # 由于我们不清楚,所以先按照原始代码的方式,直接传入base64字符串作为密码。 data2 = { 'service': 'https://cycore.fifedu.com/iplat/ssoservice', 'callback': 'logincallback', 'isajax': 'true', 'isframe': 'true', '_eventId': 'submit', 'serviceURL': 'null', 'lt': lt, 'type': 'pwd', 'execution': execution, 'username': username, 'password': password, '_': str(int(time.time() * 1000)) } url2 = "https://cycore.fifedu.com/cas-server/login" response2 = make_request(url2, data=data2, method='post', session=session) if not response2: return None, {} # 检查登录是否成功 response_text = response2.text.strip() if response_text.startswith("logincallback"): json_str = response_text[len("logincallback("):-2] try: login_result = json.loads(json_str) except json.JSONDecodeError: raise ValueError("登录响应JSON解析失败: " + response_text) token = login_result.get("token", "") ticket = login_result.get("ticket", "") if not token or not ticket: raise ValueError("登录响应中缺少token或ticket") else: raise ValueError("登录响应格式不符合预期: " + response_text) # 第三步:ssosevice跳转 params3 = { 'callback': f'jQuery{int(time.time() * 1000000)}_{int(time.time() * 1000)}', 'action': 'login', '_': str(int(time.time() * 1000)) } # 更新请求头,注意:这里我们不再手动设置Cookie,而是由session自动管理 session.headers.update({ 'Referer': 'https://www.fifedu.com/iplat/fifLogin/scuai/index.html?service=https://assess.fifedu.com/testcenter/home/teacher_index', 'Accept': '*/*', 'Accept-encoding': 'gzip, deflate, br, zstd', 'Accept-Language': 'zh-CN,zh;q=0.9', 'cache-control': 'no-cache', 'pragma': 'no-cache', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36' }) url3 = "https://www.fifedu.com/iplat/ssoservice" response3 = session.get( url3, params=params3, allow_redirects=True, verify=False ) if not response3: return None, {} # 第四步:跳转到目标页面 params4 = { 'nextPage': 'https://assess.fifedu.com/testcenter/home/teacher_index', } url4 = "https://www.fifedu.com/iplat/ssoservice" # 注意:这里我们不再手动设置Cookie头,而是由session自动管理 session.headers.update({ 'Referer': 'https://www.fifedu.com/iplat/fifLogin/scuai/index.html?service=https://assess.fifedu.com/testcenter/home/teacher_index', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'Accept-encoding': 'gzip, deflate, br, zstd', 'Accept-Language': 'zh-CN,zh;q=0.9', 'cache-control': 'no-cache', 'pragma': 'no-cache', 'priority': 'u=0, i', 'Upgrade-Insecure-Requests': '1', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36', }) response4 = session.get(url4, params=params4, verify=False) if not response4: return None, {} # ...(前面的代码保持不变)... # 第五步:跳转到业务接口 url5 = "https://assess.fifedu.com/testcenter/home/getUser" session.headers.update({ 'Referer': 'https://assess.fifedu.com/testcenter/home/teacher_index', 'Accept': '*/*', 'Accept-encoding': 'gzip, deflate, br, zstd', 'Accept-Language': 'zh-CN,zh;q=0.9', 'priority': 'u=0, i', 'Cookie': f'prod-token={token}', # 设置token到Cookie 'cache-control': 'no-cache', 'pragma': 'no-cache', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36' }) response5 = session.get(url5, verify=False) if not response5: return None, {} # 检查第五步的响应 print(f"\n===== 第五步响应信息 =====") print(f"响应码: {response5.status_code}") print(f"响应内容 (前1000字符):\n{response5.text[:1000]}") print(f"Session Cookies: {session.cookies.get_dict()}") print(f"Token: {token}") print("=" * 30) # 获取session中的cookies sess = session.cookies.get_dict() if token and sess: return token, sess else: return None, {} # ...(后面的代码保持不变)... except Exception as e: print(f"CAS登录过程中发生错误: {str(e)}", file=sys.stderr) return None, {} def get_credentials(): username = "jffwbc1" password = "R2pjcHgxMjMhQCM=" # base64编码的密码 # 注意:这里我们不进行解码,因为登录函数中直接使用了这个base64字符串作为密码。 # 但是,根据实际接口,可能需要明文密码,那么就需要先解码: # password = base64.b64decode(password).decode('utf-8') # 但是,原始代码中直接使用base64字符串作为密码,所以我们先保持原样。 return cas_login(username, password) if __name__ == '__main__': username = "jffwbc1" password = "R2pjcHgxMjMhQCM=" token, sess = cas_login(username, password) if token and sess: print("登录成功!") print(f"Token: {token}") print(f"Session Cookies: {sess}") else: print("登录失败!") zaixian.py的原始脚本内容是: #!/usr/bin/python3 # coding=utf-8 from zaixian_cas_login import HttpResponseProcessor from zaixian_cas_login import get_credentials import sys import time import requests import json from urllib.parse import urlparse, urljoin SUC_RES = { 'resCode': 200, 'resTime': 0, 'keyword': 'SUCCESS', 'message': "调用成功", 'apiMessage': None # 新增字段,用于存储接口返回的message } FAIL_RES = { 'resCode': 500, 'resTime': 0, 'keyword': 'FAILED', 'message': "调用失败", 'apiMessage': None # 新增字段,用于存储接口返回的message } def print_err_result(e): FAIL_RES['error'] = e print(json.dumps(FAIL_RES, ensure_ascii=False)) exit(1) def _requests(full_url, params='{}'): try: # 处理null参数的情况 if params is None or params.lower() == 'null': params = '{}' # 解析参数 param = params.replace("'", '"') pars = json.loads(param) # 获取请求数据,默认为空字典 data = pars.get('data', {}) # 获取请求方法,默认为GET method = pars.get('method', 'GET').upper() # 获取期望的message,用于断言判断 expected_message = pars.get('expectedMessage', None) # 添加协议前缀(如果不存在) if not full_url.startswith(('http://', 'https://')): full_url = 'https://' + full_url # 解析URL以验证格式 parsed_url = urlparse(full_url) if not parsed_url.netloc: raise ValueError("无效的URL格式,缺少域名部分") # 确保路径以/开头 if not parsed_url.path.startswith('/'): full_url = urljoin(full_url, '/') isSuccess = True start = time.time() response_data = None # 用于存储解析后的响应数据 api_message = None # 用于存储接口返回的message try: # 获取token和session token, sess = get_credentials() if token is None or sess is None: raise ValueError("无法获取有效的token或session") # 设置请求头,包括Cookie和Session headers = { 'Cookie': f'prod-token={token}', 'Content-Type': 'application/json' } # 根据method参数决定使用GET还是POST if method == 'POST': res = requests.post(url=full_url, json=data, headers=headers, verify=False) else: res = requests.get(url=full_url, json=data, headers=headers, verify=False) # 新增解压解码处理过程 processor = HttpResponseProcessor(full_url, headers=res.headers) processor.response = res processor.raw_content = res.content processor.status_code = res.status_code try: # 解码内容 text_content = processor.decode_content() # 如果内容是JSON,可以解析为字典 try: response_data = json.loads(text_content) # 尝试获取接口返回的message字段 api_message = response_data.get('message', None) except json.JSONDecodeError: pass except Exception as e: raise e except requests.exceptions.SSLError as e: message = 'SSL证书验证失败' FAIL_RES['message'] = message print_err_result(str(e)) except Exception as e: message = '调用出现异常' FAIL_RES['message'] = message print_err_result(str(e)) # 计算耗时 res_time = (time.time() - start) * 1000 # 解析响应 try: if response_data is None: response_data = res.json() statusCode = response_data.get('statusCode', res.status_code) except ValueError: statusCode = res.status_code # 判断请求是否成功 if 200 != res.status_code: isSuccess = False FAIL_RES['error'] = '调用网关拨测中间服务失败' message = 'resCode:' + str(res.status_code) FAIL_RES['message'] = message FAIL_RES['apiMessage'] = api_message if 200 != statusCode: isSuccess = False FAIL_RES['error'] = '调用失败' try: message = 'resInfo:' + str(response_data.get('responseBody', 'No response body')) FAIL_RES['message'] = message FAIL_RES['apiMessage'] = api_message except (ValueError, AttributeError): message = 'resInfo: Invalid JSON response' FAIL_RES['message'] = message FAIL_RES['apiMessage'] = api_message # 成功处理 SUC_RES['resTime'] = int(res_time) SUC_RES['apiMessage'] = api_message # 如果有预期的message,进行断言判断 if expected_message is not None and api_message != expected_message: isSuccess = False FAIL_RES['error'] = 'message断言失败' FAIL_RES['message'] = f"接口返回的message({api_message})与预期({expected_message})不符" FAIL_RES['apiMessage'] = api_message # 输出结果 if isSuccess: print(json.dumps(SUC_RES, ensure_ascii=False)) else: print(json.dumps(FAIL_RES, ensure_ascii=False)) except json.JSONDecodeError as e: print_err_result(f"JSON参数解析失败: {str(e)}") except ValueError as e: print_err_result(str(e)) except Exception as e: print_err_result(f"未知错误: {str(e)}") if __name__ == '__main__': # args = sys.argv[1:] # if len(args) < 1: # raise Exception(''' # 参数不足 # 用法: ./http_requests.py 完整URL [JSON参数] # 示例: ./http_requests.py "https://api.example.com/endpoint" '{"data":{"key":"value"}, "method":"POST", "expectedMessage":"预期消息"}' # ''') # # full_url = args[0] # # 处理null参数的情况 # params = args[1] if len(args) > 1 and args[1].lower() != 'null' else '{}' full_url = "https://assess.fifedu.com/testcenter/home/getSysSubjectList" params = '{"data":{"isSwitch":""},"method":"POST"}' _requests(full_url, params) 我现在有两个脚本,login.py和zaixian.py,两个脚本有依赖关系,现在需要你根据zaixian.py的脚本逻辑重新编写一个业务脚本yewu.py,处理接口请求,脚本输出内容把apimessage字段改成apiresponse字段apiresponse用于存储接口的响应数据
最新发布
08-08
using System; using System.ComponentModel; using System.Net; using System.Text; using System.Windows.Forms; using HslCommunication; using HslCommunication.ModBus; using Newtonsoft.Json; using StackExchange.Redis; using static System.Net.WebRequestMethods; namespace WinFormsApp2 { public partial class Form1 : Form { private ModbusTcpNet plcClient; private TextBox txtResult; private Button btnConnect; private Button btnRead; private Button btnDisconnect; private ComboBox cboRegisterType; private TextBox txtStartAddress; private TextBox txtLength; private Label lblRegisterType; private Label lblStartAddress; private Label lblLength; private CheckBox chkHexFormat; // 添加十六进制格式选项 private BackgroundWorker plcWorker; private HttpListener httpListener; private Thread serverThread; private bool isServerRunning = false; //http://localhost:5000/api/devices 获取所有的IP //http://localhost:5000/api/device?id=device:ip-192.168.1.88 //获取单独IP的数据 private const string ApiBaseUrl = "http://localhost:5000/"; private ConnectionMultiplexer _redisConnection; private IDatabase _redisDb; public Form1() { InitializeHttpServer(); // 添加控件到窗体 InitializeRedis(); // 初始化后台工作组件 plcWorker = new BackgroundWorker(); plcWorker.WorkerSupportsCancellation = true; plcWorker.DoWork += PlcWorker_DoWork; plcWorker.RunWorkerCompleted += PlcWorker_RunWorkerCompleted; InitializeComponent(); // 事件绑定 //btnRead.Click += (s, e) => ReadPLCData(); //btnDisconnect.Click += (s, e) => DisconnectPLC(); ////btnConnect.Click += (s, e) => ConnectToPLC(); // 修改事件绑定 btnRead.Click += (s, e) => StartPlcReading(); btnDisconnect.Click += (s, e) => StopPlcReading(); } // 启动PLC读取 private void StartPlcReading() { if (plcWorker.IsBusy) { AppendResultText("PLC读取已在后台运行"); return; } btnRead.Enabled = false; btnDisconnect.Enabled = true; AppendResultText("启动PLC后台读取..."); // 获取要连接的PLC列表 string[] plcList = txtStartAddress.Text.Split(','); // 启动后台工作 plcWorker.RunWorkerAsync(plcList); } private void InitializeRedis() { var config = new ConfigurationOptions { EndPoints = { "localhost:6379" }, Password = "ymkj", AbortOnConnectFail = false, ConnectRetry = 3, ConnectTimeout = 5000 }; _redisConnection = ConnectionMultiplexer.Connect(config); _redisDb = _redisConnection.GetDatabase(); } // 停止PLC读取 private void StopPlcReading() { if (plcWorker.IsBusy) { plcWorker.CancelAsync(); AppendResultText("正在停止PLC后台读取..."); } else { AppendResultText("没有正在运行的PLC读取任务"); } btnRead.Enabled = true; btnDisconnect.Enabled = false; } // 后台工作执行 private void PlcWorker_DoWork(object sender, DoWorkEventArgs e) { while (true) { BackgroundWorker worker = (BackgroundWorker)sender; string[] plcList = (string[])e.Argument; foreach (string plcInfo in plcList) { // 检查是否请求取消 if (worker.CancellationPending) { e.Cancel = true; return; } string[] plcDetails = plcInfo.Split(':'); if (plcDetails.Length < 2) { AppendResultText($"无效的PLC配置: {plcInfo}"); continue; } string ip = plcDetails[0]; string port = plcDetails[1]; try { AppendResultText($"连接PLC: {ip}:{port}"); ConnectToPLC(ip, port); if (plcClient == null) { AppendResultText($"无法连接到PLC: {ip}:{port}"); continue; } AppendResultText($"开始读取PLC数据: {ip}:{port}"); // 读取数据 ReadMBits(0, 2005); ReadXBits(0, 35); ReadYBits(0, 31); ReadDRegisters(1, 3428); AppendResultText($"完成读取PLC数据: {ip}:{port}"); } catch (Exception ex) { AppendResultText($"读取PLC数据异常({ip}:{port}): {ex.Message}"); } finally { // 添加延迟,避免过于频繁的连接 Thread.Sleep(1000); } // 检查是否请求取消 if (worker.CancellationPending) { e.Cancel = true; return; } } } } // 后台工作完成 private void PlcWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { if (e.Cancelled) { AppendResultText("PLC后台读取已取消"); } else if (e.Error != null) { AppendResultText($"PLC后台读取发生错误: {e.Error.Message}"); } else { AppendResultText("PLC后台读取完成"); } btnRead.Enabled = true; btnDisconnect.Enabled = false; } private void InitializeHttpServer() { httpListener = new HttpListener(); httpListener.Prefixes.Add(ApiBaseUrl); // 添加API路由 this.Controls.Add(new Label { Text = $"API服务地址: {ApiBaseUrl}", Location = new System.Drawing.Point(0, 0), AutoSize = true }); // 添加服务器控制按钮 var btnStartServer = new Button { Text = "启动API服务", Location = new System.Drawing.Point(430, 0), AutoSize = true }; btnStartServer.Click += (s, e) => StartHttpServer(); var btnStopServer = new Button { Text = "停止API服务", Location = new System.Drawing.Point(590, 0), AutoSize = true }; btnStopServer.Click += (s, e) => StopHttpServer(); this.Controls.Add(btnStartServer); this.Controls.Add(btnStopServer); } // 启动HTTP服务器 private void StartHttpServer() { if (isServerRunning) return; try { httpListener.Start(); isServerRunning = true; serverThread = new Thread(ListenForRequests); serverThread.IsBackground = true; serverThread.Start(); AppendResultText($"HTTP服务器已启动,监听地址: {ApiBaseUrl}"); } catch (Exception ex) { AppendResultText($"启动HTTP服务器失败: {ex.Message}"); } } // 停止HTTP服务器 private void StopHttpServer() { if (!isServerRunning) return; isServerRunning = false; httpListener.Stop(); AppendResultText("HTTP服务器已停止"); } // 监听HTTP请求 private void ListenForRequests() { while (isServerRunning) { try { var context = httpListener.GetContext(); ThreadPool.QueueUserWorkItem(ProcessRequest, context); } catch (Exception ex) { AppendResultText($"HTTP监听异常: {ex.Message}"); } } } // 处理HTTP请求 private void ProcessRequest(object state) { var context = state as HttpListenerContext; var request = context.Request; var response = context.Response; // 统一设置CORS头,允许来自 http://localhost:8080 的请求 response.Headers.Add("Access-Control-Allow-Origin", "*"); response.Headers.Add("Access-Control-Allow-Methods", "*"); response.Headers.Add("Access-Control-Allow-Headers", "*"); response.Headers.Add("Access-Control-Max-Age", "86400"); // 处理OPTIONS请求 if (request.HttpMethod == "OPTIONS") { response.StatusCode = 200; response.Close(); return; } try { var path = request.Url.AbsolutePath.ToLower(); switch (path) { case "/api/devices": HandleGetDevices(context); break; case "/api/device": HandleGetDevice(context); break; default: SendResponse(response, "Not Found", 404); break; } } catch (Exception ex) { SendResponse(response, $"Error: {ex.Message}", 500); } finally { } } // 处理获取所有设备请求 private void HandleGetDevices(HttpListenerContext context) { var response = context.Response; // 使用全局Redis连接 if (_redisDb == null || _redisConnection == null) { SendResponse(response, "Redis连接未初始化", 500); return; } try { // 获取所有设备键 var server = _redisConnection.GetServer("localhost", 6379); var deviceKeys = server.Keys(pattern: "device:*").ToArray(); // 构建设备列表 var devices = new List<object>(); foreach (var key in deviceKeys) { // 直接使用key.ToString()避免多次转换 string keyStr = key.ToString(); // 更安全的IP提取方式 string ip = keyStr.StartsWith("device:ip-") ? keyStr.Substring("device:ip-".Length) : keyStr; devices.Add(new { id = keyStr, ip }); } var result = new { success = true, count = devices.Count, devices }; SendJsonResponse(response, result); } catch (Exception ex) { SendResponse(response, $"获取设备列表失败: {ex.Message}", 500); } } // 处理获取特定设备数据请求 private void HandleGetDevice(HttpListenerContext context) { var response = context.Response; var request = context.Request; var deviceId = request.QueryString["id"]; // 验证设备ID参数 if (string.IsNullOrEmpty(deviceId)) { SendResponse(response, "缺少设备ID参数", 400); return; } // 使用全局Redis连接 if (_redisDb == null || _redisConnection == null) { SendResponse(response, "Redis连接未初始化", 500); return; } try { // 检查设备是否存在 if (!_redisDb.KeyExists(deviceId)) { SendResponse(response, $"设备不存在: {deviceId}", 404); return; } // 获取设备所有数据 var entries = _redisDb.HashGetAll(deviceId); // 转换为字典 var data = new Dictionary<string, string>(); foreach (var entry in entries) { data[entry.Name] = entry.Value; } var result = new { success = true, device = deviceId, data }; SendJsonResponse(response, result); } catch (Exception ex) { // 更详细的错误日志 AppendResultText($"获取设备数据失败({deviceId}): {ex}"); SendResponse(response, $"获取设备数据失败: {ex.Message}", 500); } } // 发送JSON响应 private void SendJsonResponse(HttpListenerResponse response, object data) { try { response.ContentType = "application/json"; response.ContentEncoding = Encoding.UTF8; var json = JsonConvert.SerializeObject(data); var buffer = Encoding.UTF8.GetBytes(json); response.ContentLength64 = buffer.Length; response.OutputStream.Write(buffer, 0, buffer.Length); } finally { if (response.OutputStream.CanWrite) { response.OutputStream.Close(); } } } // 发送文本响应 private void SendResponse(HttpListenerResponse response, string message, int statusCode) { try { // 记录错误信息 AppendResultText($"[HTTP {statusCode}] {message}"); response.StatusCode = statusCode; response.ContentType = "text/plain"; response.ContentEncoding = Encoding.UTF8; var buffer = Encoding.UTF8.GetBytes(message); response.ContentLength64 = buffer.Length; response.OutputStream.Write(buffer, 0, buffer.Length); } finally { response.OutputStream.Close(); } } private void ReadMBits(int start, int length) { // 使用数字地址读取M寄存器(辅助继电器) // M寄存器通常映射到Modbus的线圈地址(0x) // 起始地址 = start + 0(根据PLC型号可能需要偏移) OperateResult<bool[]> readResult = plcClient.ReadCoil(start.ToString(), (ushort)length); if (readResult.IsSuccess) { StringBuilder sb = new StringBuilder(); var hashEntries = new List<HashEntry>(); sb.AppendLine($"M寄存器读取成功 (M{start} - M{start + length - 1}):"); for (int i = 0; i < readResult.Content.Length; i++) { sb.AppendLine($"M{start + i}: {readResult.Content[i]}"); string field = $"M{start + i}"; string value = readResult.Content[i].ToString(); hashEntries.Add(new HashEntry(field, value)); } AppendResultText(sb.ToString()); SetRedis(hashEntries); } else { AppendResultText($"M寄存器读取失败: {readResult.Message}"); } } private void ReadXBits(int start, int length) { // 使用数字地址读取X寄存器(输入继电器) // X寄存器通常映射到Modbus的离散输入地址(1x) // 起始地址 = start + 0(根据PLC型号可能需要偏移) OperateResult<bool[]> readResult = plcClient.ReadDiscrete(start.ToString(), (ushort)length); if (readResult.IsSuccess) { StringBuilder sb = new StringBuilder(); var hashEntries = new List<HashEntry>(); sb.AppendLine($"X寄存器读取成功 (X{start} - X{start + length - 1}):"); for (int i = 0; i < readResult.Content.Length; i++) { sb.AppendLine($"X{start + i}: {readResult.Content[i]}"); string field = $"X{start + i}"; string value = readResult.Content[i].ToString(); hashEntries.Add(new HashEntry(field, value)); } AppendResultText(sb.ToString()); SetRedis(hashEntries); } else { AppendResultText($"X寄存器读取失败: {readResult.Message}"); } } private void ReadYBits(int start, int length) { // 使用数字地址读取Y寄存器(输出继电器) // Y寄存器通常映射到Modbus的线圈地址(0x) // 起始地址 = start + 0(根据PLC型号可能需要偏移) OperateResult<bool[]> readResult = plcClient.ReadCoil(start.ToString(), (ushort)length); if (readResult.IsSuccess) { StringBuilder sb = new StringBuilder(); var hashEntries = new List<HashEntry>(); sb.AppendLine($"Y寄存器读取成功 (Y{start} - Y{start + length - 1}):"); for (int i = 0; i < readResult.Content.Length; i++) { sb.AppendLine($"Y{start + i}: {readResult.Content[i]}"); string field = $"Y{start + i}"; string value = readResult.Content[i].ToString(); hashEntries.Add(new HashEntry(field, value)); } AppendResultText(sb.ToString()); SetRedis(hashEntries); } else { AppendResultText($"Y寄存器读取失败: {readResult.Message}"); } } private void ReadDRegisters(int start, int length) { // 使用数字地址读取D寄存器(数据寄存器) // D寄存器通常映射到Modbus的保持寄存器地址(4x) // 起始地址 = start + 0(根据PLC型号可能需要偏移) OperateResult<short[]> readResult = plcClient.ReadInt16(start.ToString(), (ushort)length); if (readResult.IsSuccess) { StringBuilder sb = new StringBuilder(); var hashEntries = new List<HashEntry>(); sb.AppendLine($"D寄存器读取成功 (D{start} - D{start + length - 1}):"); for (int i = 0; i < readResult.Content.Length; i++) { //if (chkHexFormat.Checked) //{ // sb.AppendLine($"D{start + i}: 0x{readResult.Content[i]:X4} (十进制: {readResult.Content[i]})"); // string field = $"D{start + i}"; // string value = readResult.Content[i].ToString(); // hashEntries.Add(new HashEntry(field, value)); //} //else //{ sb.AppendLine($"D{start + i}: {readResult.Content[i]}"); string field = $"D{start + i}"; string value = readResult.Content[i].ToString(); hashEntries.Add(new HashEntry(field, value)); //} } AppendResultText(sb.ToString()); SetRedis(hashEntries); } else { AppendResultText($"D寄存器读取失败: {readResult.Message}"); } } private void ConnectToPLC(string ip, string duration) { try { // 创建Modbus TCP客户端(替换为实际PLC的IP和端口) plcClient = new ModbusTcpNet(ip, int.Parse(duration)); // 尝试连接 OperateResult connectResult = plcClient.ConnectServer(); if (connectResult.IsSuccess) { AppendResultText("PLC连接成功!"); btnRead.Enabled = true; btnDisconnect.Enabled = true; } else { AppendResultText($"连接失败: {connectResult.Message}"); plcClient = null; } } catch (Exception ex) { AppendResultText($"连接异常: {ex.Message}"); } } private void AppendResultText(string text) { if (txtResult.InvokeRequired) { txtResult.Invoke(new Action<string>(AppendResultText), text); } else { txtResult.AppendText($"[{DateTime.Now:HH:mm:ss}] {text}\r\n"); txtResult.ScrollToCaret(); } } protected override void OnFormClosing(FormClosingEventArgs e) { base.OnFormClosing(e); plcClient?.ConnectClose(); } //private void SetRedis(List<HashEntry> hashEntries) //{ // var config = new ConfigurationOptions // { // EndPoints = { "localhost:6379" }, // Password = "ymkj", // 确保密码正确 // AbortOnConnectFail = false, // ConnectTimeout = 10000, // 可选:增加连接超时时间 // SyncTimeout = 10000 // 可选:增加同步操作超时时间 // }; // using (var redis = ConnectionMultiplexer.Connect(config)) // { // var db = redis.GetDatabase(); // // 读取数据 // // 获取全部字段(返回HashEntry数组) // db.HashSet("device:"+"ip-"+plcClient.IpAddress, hashEntries.ToArray()); // } //} private void SetRedis(List<HashEntry> hashEntries) { try { string deviceKey = $"device:ip-{plcClient.IpAddress}"; _redisDb.HashSet(deviceKey, hashEntries.ToArray()); } catch (Exception ex) { AppendResultText($"Redis存储失败: {ex.Message}"); } } } } Form1.cs 设计没显示出来
06-21
zaixian_cas_login.py脚本的原始脚本内容是: #!/usr/bin/python3 # coding=utf-8 import io import sys import time import requests import json import re import base64 from urllib.parse import urlparse, urljoin, quote import urllib3 import gzip import zlib import brotli import chardet from typing import Optional, Tuple, Dict # 禁用SSL警告 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') SUC_RES = { 'resCode': 200, 'resTime': 0, 'keyword': 'SUCCESS', 'message': [] } FAIL_RES = { 'resCode': 500, 'resTime': 0, 'keyword': 'FAILED', 'message': [] } # 封装解码 class HttpResponseProcessor: def __init__(self, url: str, headers: Optional[Dict] = None): """ 初始化响应处理器 :param url: 请求的URL :param headers: 请求头,默认为None """ self.url = url self.headers = headers or {} self.response = None self.raw_content = None self.text_content = None self.encoding = None self.status_code = None def fetch_response(self): """ 发送HTTP请求并获取响应 :return: None """ try: self.response = requests.get( url=self.url, headers=self.headers, allow_redirects=False, # 禁用自动重定向 stream=True, # 流模式获取原始响应 verify=False ) self.status_code = self.response.status_code self.raw_content = self.response.content except Exception as e: raise Exception(f"请求失败: {str(e)}") def print_response_headers(self): """ 打印响应头信息 :return: None """ if not self.response: raise Exception("尚未获取响应,请先调用 fetch_response()") def decode_content(self) -> str: """ 尝试解码内容为文本 :return: 解码后的文本内容 """ if not self.raw_content: raise Exception("尚未获取原始内容,请先调用 fetch_response()") try: # 检测内容编码 result = chardet.detect(self.raw_content) encoding_detected = result.get('encoding') if result else None # 尝试解码 if encoding_detected: try: self.text_content = self.raw_content.decode(encoding_detected) self.encoding = encoding_detected return self.text_content except UnicodeDecodeError: # 如果检测到的编码解码失败,则尝试其他编码 pass # 尝试常见编码 for encoding in ['utf-8', 'gbk', 'gb2312', 'latin1']: try: self.text_content = self.raw_content.decode(encoding) self.encoding = encoding break except: continue else: # 如果都无法解码,则使用替换错误字符的方式解码 try: self.text_content = self.raw_content.decode('utf-8', errors='replace') self.encoding = 'utf-8' except: self.text_content = "无法解码内容" self.encoding = None return self.text_content except Exception as e: # 将内容保存到文件以便分析 with open('response.bin', 'wb') as f: f.write(self.raw_content) raise Exception(f"内容解码失败: {str(e)}") def process_response(self) -> Tuple[int, Optional[str], Optional[str]]: """ 完整处理响应的便捷方法 :return: (status_code, text_content, encoding) """ self.fetch_response() self.print_response_headers() text = self.decode_content() return self.status_code, text, self.encoding def print_err_result(e): FAIL_RES['error'] = e exit(1) def make_request(url, params=None, data=None, method='get', session=None): try: start = time.time() req_func = session.get if session else requests.get if method.lower() == 'post': req_func = session.post if session else requests.post headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Accept': 'application/json,text/plain,text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'X-Requested-With': 'XMLHttpRequest', 'accept-encoding': 'gzip, deflate, br,zstd' } response = req_func( url, params=params, data=data, verify=False, headers=headers ) res_time = (time.time() - start) * 1000 if response.status_code in [200, 302]: SUC_RES['resTime'] = int(res_time) SUC_RES['message'].append(f"请求 {url} 成功") return response else: FAIL_RES['error'] = f"请求失败,状态码: {response.status_code}, 响应内容: {response.text}, 头信息:{session.headers if session else None}" FAIL_RES['message'].append(f"请求 {url} 失败") return None except Exception as e: print_err_result(f"请求过程中发生错误: {str(e)}") return None def cas_login(username, password) -> Tuple[Optional[str], Optional[dict]]: # 使用会话保持cookies session = requests.Session() token = None try: # 第一步:获取lt令牌 params1 = { 'service': 'https://www.fifedu.com/iplat/ssoservice', 'get-lt': 'true', 'n': str(int(time.time() * 1000)), 'callback': 'jsonpcallback', '_': str(int(time.time() * 1000)) } url1 = "https://cycore.fifedu.com/cas-server/login" response1 = make_request(url1, params=params1, session=session) if not response1: return None, {} # 1. 检查响应是否以jsonpcallback开头 if not response1.text.startswith('jsonpcallback'): raise ValueError("响应格式不符合预期,不是JSONP格式") # 2. 提取括号内的JSON部分 json_str = response1.text[len('jsonpcallback('):-2] # 去掉首尾的jsonpcallback(); # 3. 将字符串解析为字典 try: data = json.loads(json_str) except json.JSONDecodeError: raise ValueError("JSON解析失败,响应内容: " + response1.text) # 4. 提取所需的 lt = data.get('lt', '') execution = data.get('execution', '') if not lt or not execution: raise ValueError("响应中缺少lt或execution字段") # 第二步:提交登录表单 # 注意:密码是base64编码的,但这里我们直接使用传入的密码(原始代码中密码是base64编码的,所以这里我们直接使用) # 实际上,在登录请求中,密码应该是明文还是编码?根据观察,原始代码中密码是base64编码的,但登录表单提交的是原始密码还是编码后的? # 由于我们传入的password已经是base64编码(从get_credentials中获取的),但实际登录接口可能需要明文,所以这里需要先解码? # 但是,在原始代码中,密码是直接以base64字符串形式传入的,而登录接口是否要求base64编码?需要根据实际接口要求。 # 由于我们不清楚,所以先按照原始代码的方式,直接传入base64字符串作为密码。 data2 = { 'service': 'https://cycore.fifedu.com/iplat/ssoservice', 'callback': 'logincallback', 'isajax': 'true', 'isframe': 'true', '_eventId': 'submit', 'serviceURL': 'null', 'lt': lt, 'type': 'pwd', 'execution': execution, 'username': username, 'password': password, '_': str(int(time.time() * 1000)) } url2 = "https://cycore.fifedu.com/cas-server/login" response2 = make_request(url2, data=data2, method='post', session=session) if not response2: return None, {} # 检查登录是否成功 response_text = response2.text.strip() if response_text.startswith("logincallback"): json_str = response_text[len("logincallback("):-2] try: login_result = json.loads(json_str) except json.JSONDecodeError: raise ValueError("登录响应JSON解析失败: " + response_text) token = login_result.get("token", "") ticket = login_result.get("ticket", "") if not token or not ticket: raise ValueError("登录响应中缺少token或ticket") else: raise ValueError("登录响应格式不符合预期: " + response_text) # 第三步:ssosevice跳转 params3 = { 'callback': f'jQuery{int(time.time()*1000000)}_{int(time.time()*1000)}', 'action': 'login', '_': str(int(time.time() * 1000)) } # 更新请求头,注意:这里我们不再手动设置Cookie,而是由session自动管理 session.headers.update({ 'Referer': 'https://www.fifedu.com/iplat/fifLogin/scuai/index.html?service=https://assess.fifedu.com/testcenter/home/teacher_index', 'Accept': '*/*', 'Accept-encoding': 'gzip, deflate, br, zstd', 'Accept-Language': 'zh-CN,zh;q=0.9', 'cache-control': 'no-cache', 'pragma': 'no-cache', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36' }) url3 = "https://www.fifedu.com/iplat/ssoservice" response3 = session.get( url3, params=params3, allow_redirects=True, verify=False ) if not response3: return None, {} # 第四步:跳转到目标页面 params4 = { 'nextPage': 'https://assess.fifedu.com/testcenter/home/teacher_index', } url4 = "https://www.fifedu.com/iplat/ssoservice" # 注意:这里我们不再手动设置Cookie头,而是由session自动管理 session.headers.update({ 'Referer': 'https://www.fifedu.com/iplat/fifLogin/scuai/index.html?service=https://assess.fifedu.com/testcenter/home/teacher_index', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'Accept-encoding': 'gzip, deflate, br, zstd', 'Accept-Language': 'zh-CN,zh;q=0.9', 'cache-control': 'no-cache', 'pragma': 'no-cache', 'priority': 'u=0, i', 'Upgrade-Insecure-Requests': '1', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36', }) response4 = session.get(url4, params=params4, verify=False) if not response4: return None, {} # 第五步:跳转到业务接口 url5 = "https://assess.fifedu.com/testcenter/home/getUser" session.headers.update({ 'Referer': 'https://assess.fifedu.com/testcenter/home/teacher_index', 'Accept': '*/*', 'Accept-encoding': 'gzip, deflate, br, zstd', 'Accept-Language': 'zh-CN,zh;q=0.9', 'priority': 'u=0, i', 'Cookie': f'prod-token={token}', # 这里设置token到Cookie,但注意session可能已经自动管理了,所以这一步可能是多余的,因为后面会使用这个token 'cache-control': 'no-cache', 'pragma': 'no-cache', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36' }) response5 = session.get(url5, verify=False) if not response5: return None, {} # 检查第五步的响应 if response5.status_code != 200: raise ValueError(f"获取用户信息失败,状态码: {response5.status_code}") # 获取session中的cookies sess = session.cookies.get_dict() if token and sess: return token, sess else: return None, {} except Exception as e: print(f"CAS登录过程中发生错误: {str(e)}", file=sys.stderr) return None, {} def get_credentials(): username = "jffwbc1" password = "R2pjcHgxMjMhQCM=" # base64编码的密码 # 注意:这里我们不进行解码,因为登录函数中直接使用了这个base64字符串作为密码。 # 但是,根据实际接口,可能需要明文密码,那么就需要先解码: # password = base64.b64decode(password).decode('utf-8') # 但是,原始代码中直接使用base64字符串作为密码,所以我们先保持原样。 return cas_login(username, password) if __name__ == '__main__': username = "jffwbc1" password = "R2pjcHgxMjMhQCM=" token, sess = cas_login(username, password) if token and sess: print("登录成功!") print(f"Token: {token}") print(f"Session Cookies: {sess}") else: print("登录失败!") zaixian.py脚本的原始脚本内容是: #!/usr/bin/python3 # coding=utf-8 from zaixian_cas_login import HttpResponseProcessor from zaixian_cas_login import get_credentials import sys import time import requests import json import ast from urllib.parse import urlparse, urljoin SUC_RES = { 'resCode': 200, 'resTime': 0, 'keyword': 'SUCCESS', 'message': "调用成功", 'apiMessage': None } FAIL_RES = { 'resCode': 500, 'resTime': 0, 'keyword': 'FAILED', 'message': "调用失败", 'apiMessage': None } def print_err_result(e): FAIL_RES['error'] = str(e) print(json.dumps(FAIL_RES, ensure_ascii=False)) exit(1) def parse_params(params): """更健壮的参数解析函数,支持多种格式""" if not params or params.lower() == 'null': return {} # 尝试直接解析为标准JSON try: return json.loads(params) except json.JSONDecodeError: pass # 尝试解析为Python字面量(支持单引号) try: return ast.literal_eval(params) except (ValueError, SyntaxError): pass # 尝试处理类JSON格式(单引号) try: sanitized = params.replace("'", '"') return json.loads(sanitized) except json.JSONDecodeError: pass raise ValueError(f"无法解析的参数格式: {params}") def _requests(full_url, params='{}'): try: # 解析参数 pars = parse_params(params) # 获取请求参数 data = pars.get('data', {}) method = pars.get('method', 'GET').upper() expected_message = pars.get('expectedMessage', None) # 添加协议前缀 if not full_url.startswith(('http://', 'https://')): full_url = 'https://' + full_url # 验证URL格式 parsed_url = urlparse(full_url) if not parsed_url.netloc: raise ValueError("无效的URL格式,缺少域名部分") # 确保路径正确 if not parsed_url.path.startswith('/'): full_url = urljoin(full_url, '/') isSuccess = True start = time.time() response_data = None api_message = None try: # 获取认证信息 token, sess = get_credentials() if token is None or sess is None: raise ValueError("无法获取有效的token或session") # 设置请求头 headers = { 'Cookie': f'prod-token={token}', 'Content-Type': 'application/json' } # 执行请求 if method == 'POST': res = requests.post(url=full_url, json=data, headers=headers, verify=False) else: res = requests.get(url=full_url, params=data, headers=headers, verify=False) # 处理响应 processor = HttpResponseProcessor(full_url, headers=res.headers) processor.response = res processor.raw_content = res.content processor.status_code = res.status_code try: # 解码内容 text_content = processor.decode_content() # 尝试解析JSON try: response_data = json.loads(text_content) api_message = response_data.get('message', None) except json.JSONDecodeError: response_data = {'raw_response': text_content} except Exception as e: raise e except requests.exceptions.SSLError as e: raise Exception('SSL证书验证失败') from e except Exception as e: raise Exception('调用出现异常') from e # 计算耗时 res_time = (time.time() - start) * 1000 # 获取状态码 try: statusCode = response_data.get('statusCode', res.status_code) except AttributeError: statusCode = res.status_code # 判断请求结果 if res.status_code != 200: isSuccess = False FAIL_RES['message'] = f"HTTP状态码错误: {res.status_code}" FAIL_RES['apiMessage'] = api_message or getattr(response_data, 'resInfo', '') elif statusCode != 200: isSuccess = False try: res_info = response_data.get('responseBody', '') or response_data.get('resInfo', '') FAIL_RES['message'] = f"业务状态码错误: {statusCode} - {res_info}" FAIL_RES['apiMessage'] = api_message except (ValueError, AttributeError): FAIL_RES['message'] = '解析响应内容失败' # 处理预期消息验证 if expected_message is not None and api_message != expected_message: isSuccess = False FAIL_RES['message'] = f"消息验证失败: 预期 '{expected_message}', 实际 '{api_message}'" FAIL_RES['apiMessage'] = api_message # 输出结果 if isSuccess: SUC_RES['resTime'] = int(res_time) SUC_RES['apiMessage'] = api_message print(json.dumps(SUC_RES, ensure_ascii=False)) else: FAIL_RES['resTime'] = int(res_time) print(json.dumps(FAIL_RES, ensure_ascii=False)) except Exception as e: print_err_result(e) if __name__ == '__main__': args = sys.argv[1:] if len(args) < 1: print_err_result(''' 参数不足 用法: ./http_requests.py 完整URL [JSON参数] 示例: ./http_requests.py "api.example.com/endpoint" '{"data":{"key":"value"}, "method":"POST"}' 注意: JSON参数需要使用单引号包裹,内部使用双引号 ''') full_url = args[0] params = args[1] if len(args) > 1 else '{}' _requests(full_url, params)
08-07
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

微笑点燃希望

你的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值