SaaS2.0接入示例工程
引导式阅读
Java
SaaS2.0接入示例工程
作者
c***r
上架时间
2024-06-07 07:01:59

功能介绍

基于Spring boot构建,可以直接启动,通过修改application.properties配置文件里的server.port可以改变启动端口号, 默认访问路径为:http://127.0.0.1:8080/isv/produce ,可以通过卖家中心的应用工具菜单下的应用接入调试(新)页面生成链接用来本地调试

[创建实例]

客户购买商品并付款成功,云商店将调用本接口通知商家创建实例

  • 商家需要返回此订单的唯一ID(instanceId)。建议此ID直接使用该订单首次请求时云商店传入的businessId,以确保instanceId的唯一性。
  • 在正常购买场景中,请不要阻塞此接口,如果耗时较长,建议异步创建实例,可以先生成instanceId,然后立即返回。云商店后续会通过查询实例信息接口查询实例开通结果。

[更新实例]

按周期售卖的商品,用户试用转正、续费、退续费后,云商店调用该接口,更新实例的到期日期。  客户商品转正、续费下单或退订续费周期后,云商店将调用该接口请求商家执行业务变更,商家接口需要执行将到期日进行更新,并返回通知云商店。  商家需要保障更新实例接口通畅,如调用失败,将可能导致用户的业务被释放的风险。

[更新实例状态]

用户购买按周期计费商品后,对应的资源实例到期、违规等场景,云商店调用该接口,商家将指定的实例冻结。

[释放实例]

用户释放购买产品的实例时(到期不续费、退订等场景),云商店调用该接口,商家将对应的实例进行删除。

[实例升级]

用户对已购买的资源进行升级,升级订单付款成功后,云商店调用该接口请求商家升级用户购买的资源,商家接口需要执行资源升级,并记录用户升级后的产品数据。

[查询实例信息]

商家创建完资源后,云商店根据instanceId(支持批量查询,多个实例用英文逗号间隔开)查询资源实例的信息,商家返回实例相关信息。

[企业同步]

卖家申请应用凭证,或者买家购买关联应用凭证的SaaS商品后,在买家中心将应用与企业绑定时,云市场调用该接口请求服务商同步该企业的租户信息,服务商接口需要执行租户同步,保存租户信息,并返回通知云市场。

[应用同步]

卖家申请应用凭证,或者买家购买关联应用凭证的SaaS商品后,在买家中心将应用与企业绑定时,云市场调用该接口请求服务商同步该企业应用的认证信息,服务商接口需要执行应用同步,保存应用信息,并返回通知云市场。

[用户授权同步]

管理员被授权管理某企业后,在买家中心对企业内用户授权该企业已绑定的应用,云市场异步调用该接口请求服务商同步该企业应用的用户授权信息,服务商接口需要执行授权信息同步,保存用户授权信息,并返回通知云市场。

[部门增量同步]

管理员被授权管理某企业后,在买家中心对管理的企业执行新建、编辑、删除部门操作时,云市场调用该接口,请求服务商同步该企业的组织增量变更;服务商接口需要执行增量组织信息同步,保存信息,并返回通知云市场。

[部门全量同步]

卖家申请应用凭证,或者买家购买关联应用凭证的SaaS商品后,在买家中心将应用与企业绑定时,云市场调用该接口请求服务商同步该企业全量的组织信息,服务商接口需要执行全量组织信息同步,保存组织信息,并返回通知云市场。

可以学到什么

  • 可以完成联营SaaS2.0类商品接入指南中商家接入接口的代码开发工作。商家可以通过参考该示例代码来更有效的管理工业软件云商品,保障商品状态的及时变更。
  • 涉及场景为:对云商店请求参数进行验签、创建实例、更新实例、更新实例状态、释放实例、查询实例信息、企业同步、应用同步、用户授权同步、部门增量同步、部门全量同步。

开发流程说明

  1. 申请入驻云商店,成为商家。
  2. 云商店运营人员审核公司的资质信息。
  3. 准备生产接口服务器,根据本接入指南开发生产接口.
  4. 在卖家中心调试生产接口。
  5. 在卖家中心申请发布在线开通SaaS2.0类商品。
  6. 云商店运营人员审批通过后产品发布成功。
  7. 在卖家中心自助管理生产接口通知消息。

前置条件

  • 您需要拥有华为云商家账号。

  • 在卖家中心-应用工具-密钥管理,新增访问密钥并本地存储密钥信息:申请两条密钥,分别用来绑定【基础接口调试】和【联营SaaS同步接口调试】类型

关键代码片段

对云商店请求参数进行签名验证:

/** * 验证签名 * * @param temp * @param request * @param accessKey */ public static IMessageResp verifySignature(Map<String, String> temp, HttpServletRequest request, String accessKey) throws Exception { IMessageResp resp = new IMessageResp(); resp.setResultCode(ResultCodeEnum.SUCCESS.getResultCode()); resp.setResultMsg(ResultCodeEnum.SUCCESS.getResultMsg()); Map<String, String[]> paramsMap = request.getParameterMap(); // 获取请求时间戳 String reqTimestamp = getParamValue(paramsMap, TIMESTAMP); if (StringUtils.isEmpty(reqTimestamp)) { resp.setResultCode(ResultCodeEnum.INVALID_PARAM.getResultCode()); resp.setResultMsg(ResultCodeEnum.INVALID_PARAM.getResultMsg()); return resp; } // 获取当前UTC时间戳 long currentUTCTime = getUTCTimeLong(); // 请求时间戳与当前时间相差不超过60s if (!validateReqTime(reqTimestamp, currentUTCTime)) { resp.setResultCode(ResultCodeEnum.INVALID_PARAM.getResultCode()); resp.setResultMsg(ResultCodeEnum.INVALID_PARAM.getResultMsg()); return resp; } // 获取随机字符串 String nonce = getParamValue(paramsMap, NONCE); if (StringUtils.isEmpty(nonce)) { resp.setResultCode(ResultCodeEnum.INVALID_PARAM.getResultCode()); resp.setResultMsg(ResultCodeEnum.INVALID_PARAM.getResultMsg()); return resp; } // 获取请求里的签名 String reqSignature = getParamValue(paramsMap, SIGNATURE); if (StringUtils.isEmpty(reqSignature)) { resp.setResultCode(ResultCodeEnum.INVALID_TOKEN.getResultCode()); resp.setResultMsg(ResultCodeEnum.INVALID_TOKEN.getResultMsg()); return resp; } // 对入参进行顺序排序并排除value为空的key Map<String, String> sortedMap = new TreeMap<>(temp); sortedMap.entrySet().removeIf(entry -> Objects.isNull(entry.getValue())); String reqParams = objectMapper.writeValueAsString(sortedMap); // 加密请求体 String encryptBody = generateSignature(accessKey, reqParams); // 生成签名 String signature = generateSignature(accessKey, accessKey + nonce + reqTimestamp + encryptBody); // 判断计算后的签名与云市场请求中传递的签名是否一致 不区分大小写 if (!reqSignature.equalsIgnoreCase(signature)) { resp.setResultCode(ResultCodeEnum.INVALID_TOKEN.getResultCode()); resp.setResultMsg(ResultCodeEnum.INVALID_TOKEN.getResultMsg()); return resp; } return resp; }

如果验证不通过须返回失败,如果返回了成功则云商店默认数据同步成功,不会再重新触发数据同步;

如果验证通过后需要根据请求参数activity来区分具体同步场景,根据不同场景,商家需实现对应的业务逻辑:

/** * 根据活动类型调用对应的实现方法 */ private Map<String, Function<IsvProduceReq, IMessageResp>> initConsumers() { Map<String, Function<IsvProduceReq, IMessageResp>> consumers = new HashMap<>(11); consumers.put(Activity.NEW_INSTANCE, this::newInstance); consumers.put(Activity.REFRESH_INSTANCE, this::refreshInstance); consumers.put(Activity.UPDATE_INSTANCE_STATUS, this::updateInstanceStatus); consumers.put(Activity.RELEASE_INSTANCE, this::releaseInstance); consumers.put(Activity.QUERY_INSTANCE, this::queryInstance); consumers.put(Activity.UPGRADE_INSTANCE, this::upgradeInstance); consumers.put(Activity.AUTH_SYNC, this::defaultMethod); consumers.put(Activity.TENANT_SYNC, this::defaultMethod); consumers.put(Activity.APPLICATION_SYNC, this::defaultMethod); consumers.put(Activity.SINGLE_ORG_SYNC, this::defaultMethod); consumers.put(Activity.ALL_ORG_SYNC, this::defaultMethod); return consumers; }

详细示例代码可参见源码

参考

具体接入流程介绍参考:

联营License类商品接入指南(2.0版本):https://support.huaweicloud.com/accessg-marketplace/zh-cn_topic_0070649166.html

联营License类商品接入指南(旧版):https://support.huaweicloud.com/accessg-marketplace/zh-cn_topic_0070649151.html

通用SaaS类商品接入指南:https://support.huaweicloud.com/accessg-marketplace/zh-cn_topic_0046212972.html

联营SaaS类商品接入指南:https://support.huaweicloud.com/accessg-marketplace/zh-cn_topic_0070649101.html