功能介绍
基于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类商品接入指南中商家接入接口的代码开发工作。商家可以通过参考该示例代码来更有效的管理工业软件云商品,保障商品状态的及时变更。
- 涉及场景为:对云商店请求参数进行验签、创建实例、更新实例、更新实例状态、释放实例、查询实例信息、企业同步、应用同步、用户授权同步、部门增量同步、部门全量同步。
开发流程说明
- 申请入驻云商店,成为商家。
- 云商店运营人员审核公司的资质信息。
- 准备生产接口服务器,根据本接入指南开发生产接口.
- 在卖家中心调试生产接口。
- 在卖家中心申请发布在线开通SaaS2.0类商品。
- 云商店运营人员审批通过后产品发布成功。
- 在卖家中心自助管理生产接口通知消息。
关键代码片段
对云商店请求参数进行签名验证:
Copied!
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;
}
long currentUTCTime = getUTCTimeLong();
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;
}
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来区分具体同步场景,根据不同场景,商家需实现对应的业务逻辑:
Copied!
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
功能介绍
基于Spring boot构建,可以直接启动,通过修改application.properties配置文件里的server.port可以改变启动端口号, 默认访问路径为:http://127.0.0.1:8080/isv/produce ,可以通过卖家中心的应用工具菜单下的应用接入调试(新)页面生成链接用来本地调试
[创建实例]
客户购买商品并付款成功,云商店将调用本接口通知商家创建实例
[更新实例]
按周期售卖的商品,用户试用转正、续费、退续费后,云商店调用该接口,更新实例的到期日期。 客户商品转正、续费下单或退订续费周期后,云商店将调用该接口请求商家执行业务变更,商家接口需要执行将到期日进行更新,并返回通知云商店。 商家需要保障更新实例接口通畅,如调用失败,将可能导致用户的业务被释放的风险。
[更新实例状态]
用户购买按周期计费商品后,对应的资源实例到期、违规等场景,云商店调用该接口,商家将指定的实例冻结。
[释放实例]
用户释放购买产品的实例时(到期不续费、退订等场景),云商店调用该接口,商家将对应的实例进行删除。
[实例升级]
用户对已购买的资源进行升级,升级订单付款成功后,云商店调用该接口请求商家升级用户购买的资源,商家接口需要执行资源升级,并记录用户升级后的产品数据。
[查询实例信息]
商家创建完资源后,云商店根据instanceId(支持批量查询,多个实例用英文逗号间隔开)查询资源实例的信息,商家返回实例相关信息。
[企业同步]
卖家申请应用凭证,或者买家购买关联应用凭证的SaaS商品后,在买家中心将应用与企业绑定时,云市场调用该接口请求服务商同步该企业的租户信息,服务商接口需要执行租户同步,保存租户信息,并返回通知云市场。
[应用同步]
卖家申请应用凭证,或者买家购买关联应用凭证的SaaS商品后,在买家中心将应用与企业绑定时,云市场调用该接口请求服务商同步该企业应用的认证信息,服务商接口需要执行应用同步,保存应用信息,并返回通知云市场。
[用户授权同步]
管理员被授权管理某企业后,在买家中心对企业内用户授权该企业已绑定的应用,云市场异步调用该接口请求服务商同步该企业应用的用户授权信息,服务商接口需要执行授权信息同步,保存用户授权信息,并返回通知云市场。
[部门增量同步]
管理员被授权管理某企业后,在买家中心对管理的企业执行新建、编辑、删除部门操作时,云市场调用该接口,请求服务商同步该企业的组织增量变更;服务商接口需要执行增量组织信息同步,保存信息,并返回通知云市场。
[部门全量同步]
卖家申请应用凭证,或者买家购买关联应用凭证的SaaS商品后,在买家中心将应用与企业绑定时,云市场调用该接口请求服务商同步该企业全量的组织信息,服务商接口需要执行全量组织信息同步,保存组织信息,并返回通知云市场。
可以学到什么
开发流程说明
前置条件
您需要拥有华为云商家账号。
在卖家中心-应用工具-密钥管理,新增访问密钥并本地存储密钥信息:申请两条密钥,分别用来绑定【基础接口调试】和【联营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