From c6f9726ef3a52467dbde6f8d982bc258d1a3f71c Mon Sep 17 00:00:00 2001 From: Hai Liang Wang Date: Sun, 24 Sep 2023 07:48:08 +0800 Subject: [PATCH] https://gitee.com/cskefu/cskefu/issues/I836RO enable writedown qutoa for user account creation Signed-off-by: Hai Liang Wang --- .../java/com/cskefu/cc/aspect/UserAspect.java | 47 +++++ .../java/com/cskefu/cc/basic/Constants.java | 7 +- .../java/com/cskefu/cc/basic/MainContext.java | 33 ++- ...icenseCtrl.java => LicenseController.java} | 15 +- .../cc/controller/api/ApiUserController.java | 12 +- .../cc/exception/BillingQuotaException.java | 44 ++++ .../exception/BillingResourceException.java | 20 ++ .../com/cskefu/cc/model/ExecuteResult.java | 91 ++++++++ .../com/cskefu/cc/proxy/LicenseProxy.java | 199 ++++++++++++++++-- .../java/com/cskefu/cc/proxy/UserProxy.java | 45 ++-- .../resources/static/js/CSKeFu_Admin.v1.js | 30 +++ .../resources/templates/admin/user/index.pug | 2 + 12 files changed, 500 insertions(+), 45 deletions(-) create mode 100644 contact-center/app/src/main/java/com/cskefu/cc/aspect/UserAspect.java rename contact-center/app/src/main/java/com/cskefu/cc/controller/admin/{LicenseCtrl.java => LicenseController.java} (97%) create mode 100644 contact-center/app/src/main/java/com/cskefu/cc/exception/BillingQuotaException.java create mode 100644 contact-center/app/src/main/java/com/cskefu/cc/exception/BillingResourceException.java create mode 100644 contact-center/app/src/main/java/com/cskefu/cc/model/ExecuteResult.java diff --git a/contact-center/app/src/main/java/com/cskefu/cc/aspect/UserAspect.java b/contact-center/app/src/main/java/com/cskefu/cc/aspect/UserAspect.java new file mode 100644 index 00000000..1f67cc90 --- /dev/null +++ b/contact-center/app/src/main/java/com/cskefu/cc/aspect/UserAspect.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd. + * , Licensed under the Chunsong Public + * License, Version 1.0 (the "License"), https://docs.cskefu.com/licenses/v1.html + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.cskefu.cc.aspect; + +import com.cskefu.cc.basic.MainContext; +import com.cskefu.cc.exception.BillingQuotaException; +import com.cskefu.cc.exception.BillingResourceException; +import com.cskefu.cc.model.User; +import com.cskefu.cc.proxy.LicenseProxy; +import org.apache.commons.lang3.StringUtils; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Aspect +@Component +public class UserAspect { + + private final static Logger logger = LoggerFactory.getLogger(UserAspect.class); + + @Autowired + private LicenseProxy licenseProxy; + + @Before("execution(* com.cskefu.cc.persistence.repository.UserRepository.save(..))") + public void beforeSave(final JoinPoint joinPoint) throws BillingResourceException, BillingQuotaException { + final User user = (User) joinPoint.getArgs()[0]; + logger.info("[save] before user id {}", user.getId()); + if (StringUtils.isBlank(user.getId())) { + // 执行配额扣除 + licenseProxy.writeDownResourceUsageInStore(MainContext.BillingResource.USER, 1); + } else { + // update existed user + } + } +} diff --git a/contact-center/app/src/main/java/com/cskefu/cc/basic/Constants.java b/contact-center/app/src/main/java/com/cskefu/cc/basic/Constants.java index fb637423..8fc7cdfe 100644 --- a/contact-center/app/src/main/java/com/cskefu/cc/basic/Constants.java +++ b/contact-center/app/src/main/java/com/cskefu/cc/basic/Constants.java @@ -220,12 +220,13 @@ public class Constants { /** * License */ - public static final String PRODUCT_BASIC_ID = "cskefu001"; + public static final String PRODUCT_ID_CSKEFU001 = "cskefu001"; public static final String LICENSE_SERVER_INST_ID = "SERVERINSTID"; public static final String LICENSE_SERVICE_NAME = "SERVICENAME"; public static final String LICENSE_SERVICE_NAME_PREFIX = "春松客服"; public static final String LICENSEIDS = "LICENSEIDS"; public static final String METAKV_DATATYPE_STRING = "string"; + public static final String METAKV_DATATYPE_INT = "int"; public static final String SHORTID = "shortId"; public static final String LICENSES = "licenses"; public static final String ADDDATE = "addDate"; @@ -234,4 +235,8 @@ public class Constants { public static final String STATUS = "status"; public static final String PRODUCT = "product"; public static final String LICENSESTOREPROVIDER = "licenseStoreProvider"; + public static final String USER = "user"; + public static final String RESOURCES_USAGE_KEY_PREFIX = "RESOURCES_USAGE"; + public static final String NEW_USER_SUCCESS = "new_user_success"; + public static final String PRODUCT_ID = "productId"; } diff --git a/contact-center/app/src/main/java/com/cskefu/cc/basic/MainContext.java b/contact-center/app/src/main/java/com/cskefu/cc/basic/MainContext.java index 753d6d74..8e3f25ce 100644 --- a/contact-center/app/src/main/java/com/cskefu/cc/basic/MainContext.java +++ b/contact-center/app/src/main/java/com/cskefu/cc/basic/MainContext.java @@ -1,15 +1,15 @@ /* - * Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd. - * , Licensed under the Chunsong Public + * Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd. + * , Licensed under the Chunsong Public * License, Version 1.0 (the "License"), https://docs.cskefu.com/licenses/v1.html * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * Copyright (C) 2018- Jun. 2023 Chatopera Inc, , Licensed under the Apache License, Version 2.0, + * Copyright (C) 2018- Jun. 2023 Chatopera Inc, , Licensed under the Apache License, Version 2.0, * http://www.apache.org/licenses/LICENSE-2.0 - * Copyright (C) 2017 优客服-多渠道客服系统, Licensed under the Apache License, Version 2.0, + * Copyright (C) 2017 优客服-多渠道客服系统, Licensed under the Apache License, Version 2.0, * http://www.apache.org/licenses/LICENSE-2.0 */ @@ -929,6 +929,31 @@ public class MainContext { } } + /** + * 计费资源 + */ + public enum BillingResource { + USER, // 系统用户 + AGENGUSER, // 访客会话 + CONTACT, // 联系人 + ORGAN, // 组织机构 + CHANNELWEBIM; // 网页渠道 + + @Override + public String toString() { + return super.toString().toLowerCase(); + } + + public static BillingResource toValue(final String str) { + for (final BillingResource item : values()) { + if (StringUtils.equalsIgnoreCase(item.toString(), str)) { + return item; + } + } + throw new IllegalArgumentException(); + } + } + public static void setApplicationContext(ApplicationContext context) { applicationContext = context; context.getBean(TerminateBean.class); diff --git a/contact-center/app/src/main/java/com/cskefu/cc/controller/admin/LicenseCtrl.java b/contact-center/app/src/main/java/com/cskefu/cc/controller/admin/LicenseController.java similarity index 97% rename from contact-center/app/src/main/java/com/cskefu/cc/controller/admin/LicenseCtrl.java rename to contact-center/app/src/main/java/com/cskefu/cc/controller/admin/LicenseController.java index fd1d67d6..1fef4837 100644 --- a/contact-center/app/src/main/java/com/cskefu/cc/controller/admin/LicenseCtrl.java +++ b/contact-center/app/src/main/java/com/cskefu/cc/controller/admin/LicenseController.java @@ -38,8 +38,8 @@ import java.util.List; @Controller @RequestMapping("/admin/license") -public class LicenseCtrl extends Handler { - private final static Logger logger = LoggerFactory.getLogger(LicenseCtrl.class); +public class LicenseController extends Handler { + private final static Logger logger = LoggerFactory.getLogger(LicenseController.class); @Autowired private LicenseProxy licenseProxy; @@ -50,7 +50,7 @@ public class LicenseCtrl extends Handler { User user = super.getUser(request); if (user.isSuperadmin()) { try { - List licenses = licenseProxy.getLicensesFromStore(); + List licenses = licenseProxy.getLicensesInStore(); map.addAttribute(Constants.UPDATETIME, new Date()); map.addAttribute(Constants.LICENSES, licenses); map.addAttribute(Constants.LICENSESTOREPROVIDER, licenseProxy.getLicenseStoreProvider()); @@ -119,11 +119,11 @@ public class LicenseCtrl extends Handler { licenseProxy.existLicenseInStore(licenseShortId); // 验证该证书的所属产品没有现在没有其它证书:同一个产品最多只有一个证书 - JSONObject licBasic = licenseProxy.getLicenseBasicsFromStore(licenseShortId); + JSONObject licBasic = licenseProxy.getLicenseBasicsInStore(licenseShortId); final String productId = licBasic.getJSONObject(Constants.PRODUCT).getString(Constants.SHORTID); boolean isProductAdded = false; - JSONArray addedLicenseBasicsFromStore = licenseProxy.getAddedLicenseBasicsFromStore(); + JSONArray addedLicenseBasicsFromStore = licenseProxy.getAddedLicenseBasicsInStore(); for (int i = 0; i < addedLicenseBasicsFromStore.length(); i++) { JSONObject item = (JSONObject) addedLicenseBasicsFromStore.get(i); @@ -145,11 +145,12 @@ public class LicenseCtrl extends Handler { JSONObject licenseKvData = new JSONObject(); licenseKvData.put(Constants.SHORTID, licenseShortId); licenseKvData.put(Constants.ADDDATE, new Date()); + licenseKvData.put(Constants.PRODUCT_ID, productId); currents.put(0, licenseKvData); licenseProxy.createOrUpdateMetaKv(Constants.LICENSEIDS, currents.toString(), Constants.METAKV_DATATYPE_STRING); // 跳转回到证书列表 - List licenses = licenseProxy.getLicensesFromStore(); + List licenses = licenseProxy.getLicensesInStore(); map.addAttribute(Constants.LICENSES, licenses); map.addAttribute("updateTime", new Date()); map.addAttribute(Constants.LICENSESTOREPROVIDER, licenseProxy.getLicenseStoreProvider()); @@ -197,7 +198,7 @@ public class LicenseCtrl extends Handler { licenseProxy.createOrUpdateMetaKv(Constants.LICENSEIDS, post.toString(), Constants.METAKV_DATATYPE_STRING); // 跳转回到证书列表 - List licenses = licenseProxy.getLicensesFromStore(); + List licenses = licenseProxy.getLicensesInStore(); map.addAttribute(Constants.LICENSES, licenses); map.addAttribute("updateTime", new Date()); map.addAttribute(Constants.LICENSESTOREPROVIDER, licenseProxy.getLicenseStoreProvider()); diff --git a/contact-center/app/src/main/java/com/cskefu/cc/controller/api/ApiUserController.java b/contact-center/app/src/main/java/com/cskefu/cc/controller/api/ApiUserController.java index 86cfb791..08956ef1 100644 --- a/contact-center/app/src/main/java/com/cskefu/cc/controller/api/ApiUserController.java +++ b/contact-center/app/src/main/java/com/cskefu/cc/controller/api/ApiUserController.java @@ -1,15 +1,15 @@ /* - * Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd. - * , Licensed under the Chunsong Public + * Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd. + * , Licensed under the Chunsong Public * License, Version 1.0 (the "License"), https://docs.cskefu.com/licenses/v1.html * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * Copyright (C) 2018- Jun. 2023 Chatopera Inc, , Licensed under the Apache License, Version 2.0, + * Copyright (C) 2018- Jun. 2023 Chatopera Inc, , Licensed under the Apache License, Version 2.0, * http://www.apache.org/licenses/LICENSE-2.0 - * Copyright (C) 2017 优客服-多渠道客服系统, Licensed under the Apache License, Version 2.0, + * Copyright (C) 2017 优客服-多渠道客服系统, Licensed under the Apache License, Version 2.0, * http://www.apache.org/licenses/LICENSE-2.0 */ package com.cskefu.cc.controller.api; @@ -183,7 +183,9 @@ public class ApiUserController extends Handler { User user = userProxy.parseUserFromJson(payload); JsonObject resp = userProxy.createNewUser(user, parentOrgan); - if (StringUtils.isNotEmpty(roleId)) { + if (StringUtils.isNotEmpty(roleId) && + StringUtils.equals(resp.get(RestUtils.RESP_KEY_DATA).getAsString(), + Constants.NEW_USER_SUCCESS)) { Role role = roleRes.findById(roleId).orElse(null); UserRole userRole = new UserRole(); userRole.setUser(user); diff --git a/contact-center/app/src/main/java/com/cskefu/cc/exception/BillingQuotaException.java b/contact-center/app/src/main/java/com/cskefu/cc/exception/BillingQuotaException.java new file mode 100644 index 00000000..77f91df5 --- /dev/null +++ b/contact-center/app/src/main/java/com/cskefu/cc/exception/BillingQuotaException.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd. + * , Licensed under the Chunsong Public + * License, Version 1.0 (the "License"), https://docs.cskefu.com/licenses/v1.html + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * Copyright (C) 2019-Jun. 2023 Chatopera Inc, , + * Licensed under the Apache License, Version 2.0, + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package com.cskefu.cc.exception; + +public class BillingQuotaException extends Exception { + + public final static String SUFFIX = "billingquotaexception."; + + // metakv 中没有找到可以支持配额的证书类型 + public final static String NO_LICENSE_FOUND = SUFFIX + "no_license_found"; + // 返回值异常,可能是网络连接不上,稍后再试 + public static final String RESPONSE_UNEXPECTED = SUFFIX + "response_unexpected"; + // 请求参数不合法 + public static final String INVALID_REQUEST_BODY = SUFFIX + "invalid_request_body"; + // 证书在证书商店不存在 + public static final String LICENSE_INVALID = SUFFIX + "license_invalid"; + // 证书关联的产品信息不合法 + public static final String PRODUCT_INVALID = SUFFIX + "product_invalid"; + // 证书失效或耗尽 + public static final String LICENSE_EXPIRED_OR_EXHAUSTED = SUFFIX + "license_expired_or_exhausted"; + // 证书关闭了对该 serverinst 的支持 + public static final String LICENSE_DISABLED_SERVERINST = SUFFIX + "license_disabled_serverinst"; + // 证书不支持配额回退 + public static final String LICENSE_UNSUPPORT_REFUND = SUFFIX + "license_unsupport_refund"; + // 证书配额余量不足,不能完成本次请求 + public static final String LICENSE_QUOTA_INADEQUATE = SUFFIX + "license_quota_inadequate"; + // 内部错误,不应该发生 + public static final String INTERNAL_ERROR = SUFFIX + "internal_error"; + + public BillingQuotaException(final String s) { + super(s); + } +} diff --git a/contact-center/app/src/main/java/com/cskefu/cc/exception/BillingResourceException.java b/contact-center/app/src/main/java/com/cskefu/cc/exception/BillingResourceException.java new file mode 100644 index 00000000..e7e38f30 --- /dev/null +++ b/contact-center/app/src/main/java/com/cskefu/cc/exception/BillingResourceException.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd. + * , Licensed under the Chunsong Public + * License, Version 1.0 (the "License"), https://docs.cskefu.com/licenses/v1.html + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * Copyright (C) 2019-Jun. 2023 Chatopera Inc, , + * Licensed under the Apache License, Version 2.0, + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package com.cskefu.cc.exception; + +public class BillingResourceException extends Exception{ + public BillingResourceException(final String s){ + super(s); + } +} diff --git a/contact-center/app/src/main/java/com/cskefu/cc/model/ExecuteResult.java b/contact-center/app/src/main/java/com/cskefu/cc/model/ExecuteResult.java new file mode 100644 index 00000000..adadc46d --- /dev/null +++ b/contact-center/app/src/main/java/com/cskefu/cc/model/ExecuteResult.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd. + * , Licensed under the Chunsong Public + * License, Version 1.0 (the "License"), https://docs.cskefu.com/licenses/v1.html + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.cskefu.cc.model; + +import org.json.JSONObject; + +import java.io.Serializable; + +public class ExecuteResult implements Serializable { + public final static int RC_SUCC = 0; + public final static int RC_ERR1 = 1; + public final static int RC_ERR2 = 2; + public final static int RC_ERR3 = 3; + public final static int RC_ERR4 = 4; + public final static int RC_ERR5 = 5; + public final static int RC_ERR6 = 6; + public final static int RC_ERR7 = 7; + public final static int RC_ERR8 = 8; + public final static int RC_ERR9 = 9; + + private int rc; // 0 for success, errors other + private String error; + private String msg; + private JSONObject data; + + + public ExecuteResult() { + + } + + public ExecuteResult(final int rc, final String msg) { + this.rc = rc; + this.msg = msg; + } + + public ExecuteResult(final int rc, final String msg, final String error) { + this.rc = rc; + this.msg = msg; + this.error = error; + } + + public ExecuteResult(final int rc, + final String msg, + final String error, + final JSONObject data) { + this.rc = rc; + this.msg = msg; + this.error = error; + this.data = data; + } + + public int getRc() { + return rc; + } + + public void setRc(int rc) { + this.rc = rc; + } + + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public JSONObject getData() { + return data; + } + + public void setData(JSONObject data) { + this.data = data; + } +} diff --git a/contact-center/app/src/main/java/com/cskefu/cc/proxy/LicenseProxy.java b/contact-center/app/src/main/java/com/cskefu/cc/proxy/LicenseProxy.java index 81e918f0..f99bc2bb 100644 --- a/contact-center/app/src/main/java/com/cskefu/cc/proxy/LicenseProxy.java +++ b/contact-center/app/src/main/java/com/cskefu/cc/proxy/LicenseProxy.java @@ -18,9 +18,8 @@ import com.chatopera.store.sdk.exceptions.InvalidResponseException; import com.cskefu.cc.basic.Constants; import com.cskefu.cc.basic.MainContext; import com.cskefu.cc.basic.MainUtils; -import com.cskefu.cc.exception.LicenseNotFoundException; -import com.cskefu.cc.exception.MetaKvInvalidKeyException; -import com.cskefu.cc.exception.MetaKvNotExistException; +import com.cskefu.cc.exception.*; +import com.cskefu.cc.model.ExecuteResult; import com.cskefu.cc.model.MetaKv; import com.cskefu.cc.persistence.repository.MetaKvRepository; import com.cskefu.cc.util.Base62; @@ -50,6 +49,16 @@ public class LicenseProxy { @Autowired private QuotaWdClient quotaWdClient; + private static final Map BILLING_RES_QUOTA_MAPPINGS = new HashMap<>(); + + static { + BILLING_RES_QUOTA_MAPPINGS.put(MainContext.BillingResource.USER, 100); + BILLING_RES_QUOTA_MAPPINGS.put(MainContext.BillingResource.AGENGUSER, 1); + BILLING_RES_QUOTA_MAPPINGS.put(MainContext.BillingResource.CHANNELWEBIM, 100); + BILLING_RES_QUOTA_MAPPINGS.put(MainContext.BillingResource.CONTACT, 1); + BILLING_RES_QUOTA_MAPPINGS.put(MainContext.BillingResource.ORGAN, 10); + } + /** * 初始化 serverinstId * serverinstId 作为服务唯一的实例ID @@ -76,20 +85,31 @@ public class LicenseProxy { /** * Init local data for License */ + resolveServerinstId(); + resolveServicename(); + resolveLicenseIds(); + } + + /** + * 读取或初始化 serverinstId + * + * @return + */ + private String resolveServerinstId() { Optional metaServerinstIdOpt = metaKvRes.findFirstByMetakey(Constants.LICENSE_SERVER_INST_ID); if (metaServerinstIdOpt.isEmpty()) { // 没有 serverinstId 信息,初始化 final String serverinstId = MainUtils.getUUID(); createMetaKv(Constants.LICENSE_SERVER_INST_ID, serverinstId, Constants.METAKV_DATATYPE_STRING); + return serverinstId; } + return metaServerinstIdOpt.get().getMetavalue(); + } - Optional metaServicenameOpt = metaKvRes.findFirstByMetakey(Constants.LICENSE_SERVICE_NAME); - if (metaServicenameOpt.isEmpty()) { - // 没有 Service Name 信息,初始化 - final String serviceName = generateLicenseServiceName(); - createMetaKv(Constants.LICENSE_SERVICE_NAME, serviceName, Constants.METAKV_DATATYPE_STRING); - } - + /** + * 读取或初始化 licenseIds + */ + private void resolveLicenseIds() { Optional metaLicensesOpt = metaKvRes.findFirstByMetakey(Constants.LICENSEIDS); if (metaLicensesOpt.isEmpty()) { // 没有 license 信息,初始化 @@ -97,6 +117,22 @@ public class LicenseProxy { } } + /** + * 读取或初始化 serviceName + * + * @return + */ + private String resolveServicename() { + Optional metaServicenameOpt = metaKvRes.findFirstByMetakey(Constants.LICENSE_SERVICE_NAME); + if (metaServicenameOpt.isEmpty()) { + // 没有 Service Name 信息,初始化 + final String serviceName = generateLicenseServiceName(); + createMetaKv(Constants.LICENSE_SERVICE_NAME, serviceName, Constants.METAKV_DATATYPE_STRING); + return serviceName; + } + return metaServicenameOpt.get().getMetavalue(); + } + /** * 从 MetaKv 表中取得数据 MetaKv * @@ -159,6 +195,27 @@ public class LicenseProxy { return metakv; } + /** + * 增加 MetaKv 中的 Key 的值,作为 Integer 做增量,不存在则初始化其值为 0,然后增量操作 + * + * @param key + * @param incrValue + */ + public MetaKv increValueInMetaKv(final String key, final int incrValue) { + try { + MetaKv kv = retrieveMetaKv(key); + int pre = Integer.parseInt(kv.getMetavalue()); + kv.setMetavalue(Integer.toString(pre + incrValue)); + kv.setUpdatetime(new Date()); + metaKvRes.save(kv); + return kv; + } catch (MetaKvNotExistException e) { + return createMetaKv(key, Integer.toString(incrValue), Constants.METAKV_DATATYPE_INT); + } catch (MetaKvInvalidKeyException e) { + throw new RuntimeException(e); + } + } + /** * 生成随机字符串,作为服务名称 * @@ -178,7 +235,7 @@ public class LicenseProxy { * * @return */ - public List getLicensesFromStore() throws InvalidResponseException { + public List getLicensesInStore() throws InvalidResponseException { List result = new ArrayList<>(); try { @@ -240,7 +297,7 @@ public class LicenseProxy { * @return * @throws InvalidResponseException */ - public JSONObject getLicenseBasicsFromStore(final String licenseShortId) throws InvalidResponseException, InvalidRequestException { + public JSONObject getLicenseBasicsInStore(final String licenseShortId) throws InvalidResponseException, InvalidRequestException { Response resp = quotaWdClient.getLicenseBasics(licenseShortId); if (resp.getRc() == 0) { JSONArray data = (JSONArray) resp.getData(); @@ -260,7 +317,7 @@ public class LicenseProxy { * @return * @throws InvalidResponseException */ - public JSONArray getAddedLicenseBasicsFromStore() throws InvalidResponseException { + public JSONArray getAddedLicenseBasicsInStore() throws InvalidResponseException { JSONArray arr = getLicensesInMetakv(); List ids = new ArrayList<>(); @@ -314,4 +371,120 @@ public class LicenseProxy { public String getLicenseStoreProvider() { return quotaWdClient.getBaseUrl(); } + + /** + * 获得资源用量存储 + * + * @param resourceKey + * @return + */ + public String getResourceUsageKey(final String resourceKey) { + StringBuffer sb = new StringBuffer(); + sb.append(Constants.RESOURCES_USAGE_KEY_PREFIX); + sb.append("_"); + sb.append(StringUtils.toRootUpperCase(resourceKey)); + return sb.toString(); + } + + + /** + * 增加计费资源用量 + * + * @param billingResource + * @param consume + */ + public void increResourceUsageInMetaKv(final MainContext.BillingResource billingResource, int consume) throws BillingResourceException { + switch (billingResource) { + case USER: + case CONTACT: + case ORGAN: + case AGENGUSER: + case CHANNELWEBIM: + increValueInMetaKv(getResourceUsageKey(billingResource.toString()), consume); + break; + default: + throw new BillingResourceException("invalid_billing_resource_type"); + } + } + + /** + * 获得春松客服 cskefu001 产品的证书标识 ID + * 春松客服证书基本类型 + * + * @return + */ + private String getLicenseIdAsCskefu001InMetaKv() throws BillingQuotaException { + JSONArray curr = getLicensesInMetakv(); + for (int i = 0; i < curr.length(); i++) { + JSONObject jo = (JSONObject) curr.get(i); + if (jo.has(Constants.PRODUCT_ID) && + StringUtils.equals(jo.getString(Constants.PRODUCT_ID), Constants.PRODUCT_ID_CSKEFU001)) { + return jo.getString(Constants.SHORTID); + } + } + throw new BillingQuotaException(BillingQuotaException.NO_LICENSE_FOUND); + } + + /** + * 执行配额变更操作 + * + * @param billingResource + * @param consume + * @return + */ + public ExecuteResult writeDownResourceUsageInStore(final MainContext.BillingResource billingResource, int consume) throws BillingQuotaException, BillingResourceException { + ExecuteResult er = new ExecuteResult(); + + // 请求操作配额 + String licenseId = getLicenseIdAsCskefu001InMetaKv(); + String serverinstId = resolveServerinstId(); + String servicename = resolveServicename(); + + try { + Response resp = quotaWdClient.write(licenseId, + serverinstId, servicename, consume * BILLING_RES_QUOTA_MAPPINGS.get(billingResource)); + // 识别操作是否完成,并处理 + if (resp.getRc() == 0) { + final JSONObject data = (JSONObject) resp.getData(); + + // 配额操作成功,执行计数 + increResourceUsageInMetaKv(billingResource, consume); + } else if (resp.getRc() == 1 || resp.getRc() == 2) { + throw new BillingQuotaException(BillingQuotaException.INVALID_REQUEST_BODY); + } else if (resp.getRc() == 3) { + // 证书商店中不存在 + throw new BillingQuotaException(BillingQuotaException.LICENSE_INVALID); + } else if (resp.getRc() == 4) { + // 证书商店中不存在该产品或产品类型无效 + throw new BillingQuotaException(BillingQuotaException.PRODUCT_INVALID); + } else if (resp.getRc() == 5) { + // 该证书不支持资源回退 + throw new BillingQuotaException(BillingQuotaException.LICENSE_UNSUPPORT_REFUND); + } else if (resp.getRc() == 6) { + // 证书失效或耗尽,不支持继续扣除配额 + throw new BillingQuotaException(BillingQuotaException.LICENSE_EXPIRED_OR_EXHAUSTED); + } else if (resp.getRc() == 7) { + // 该证书禁用了该 serverinstId + throw new BillingQuotaException(BillingQuotaException.LICENSE_DISABLED_SERVERINST); + } else if (resp.getRc() == 8) { + // 配额扣除额度超过该证书目前的剩余量 + throw new BillingQuotaException(BillingQuotaException.LICENSE_QUOTA_INADEQUATE); + } else { + // 未知情况 + logger.error("[writeDownResourceUsageInStore] resp data {}", resp.toString()); + throw new BillingQuotaException(BillingQuotaException.INTERNAL_ERROR); + } + } catch (InvalidResponseException e) { + // TODO 处理异常信息 + logger.error("[writeDownResourceUsageInStore] error ", e); + throw new BillingQuotaException(BillingQuotaException.RESPONSE_UNEXPECTED); + } + +// er.setRc(ExecuteResult.RC_ERR1); +// if (er.getRc() != ExecuteResult.RC_SUCC) { +// throw new BillingQuotaException(BillingQuotaException.NO_LICENSE_FOUND); +// } + + return er; + } } diff --git a/contact-center/app/src/main/java/com/cskefu/cc/proxy/UserProxy.java b/contact-center/app/src/main/java/com/cskefu/cc/proxy/UserProxy.java index 74309ac3..83f2f086 100644 --- a/contact-center/app/src/main/java/com/cskefu/cc/proxy/UserProxy.java +++ b/contact-center/app/src/main/java/com/cskefu/cc/proxy/UserProxy.java @@ -1,13 +1,13 @@ -/* - * Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd. - * , Licensed under the Chunsong Public +/* + * Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd. + * , Licensed under the Chunsong Public * License, Version 1.0 (the "License"), https://docs.cskefu.com/licenses/v1.html * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * Copyright (C) 2019-2022 Chatopera Inc, All rights reserved. + * Copyright (C) 2019-2022 Chatopera Inc, All rights reserved. * */ @@ -16,6 +16,7 @@ package com.cskefu.cc.proxy; import com.cskefu.cc.basic.Constants; import com.cskefu.cc.basic.MainContext; import com.cskefu.cc.basic.MainUtils; +import com.cskefu.cc.exception.BillingQuotaException; import com.cskefu.cc.model.*; import com.cskefu.cc.persistence.repository.*; import com.cskefu.cc.util.restapi.RestUtils; @@ -29,6 +30,8 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Component; import jakarta.persistence.criteria.Predicate; + +import java.lang.reflect.UndeclaredThrowableException; import java.util.*; import java.util.stream.Collectors; @@ -79,22 +82,34 @@ public class UserProxy { public JsonObject createNewUser(final User user, Organ organ) { JsonObject result = new JsonObject(); String msg = validUser(user); - if (StringUtils.equalsIgnoreCase(msg, "new_user_success")) { + + if (StringUtils.equalsIgnoreCase(msg, Constants.NEW_USER_SUCCESS)) { // 此时 msg 是 new_user_success user.setSuperadmin(false); // 不支持创建第二个系统管理员 + try { + if (StringUtils.isNotBlank(user.getPassword())) { + user.setPassword(MainUtils.md5(user.getPassword())); + } + userRes.save(user); - if (StringUtils.isNotBlank(user.getPassword())) { - user.setPassword(MainUtils.md5(user.getPassword())); - } - userRes.save(user); - - if (organ != null) { - OrganUser ou = new OrganUser(); - ou.setUserid(user.getId()); - ou.setOrgan(organ.getId()); - organUserRes.save(ou); + if (organ != null) { + OrganUser ou = new OrganUser(); + ou.setUserid(user.getId()); + ou.setOrgan(organ.getId()); + organUserRes.save(ou); + } + } catch (Exception e) { + if (e instanceof UndeclaredThrowableException) { + logger.error("[createNewUser] BillingQuotaException", e); + if (StringUtils.startsWith(e.getCause().getMessage(), BillingQuotaException.SUFFIX)) { + msg = e.getCause().getMessage(); + } + } else { + logger.error("[createNewUser] err", e); + } } } + // 新账号未通过验证,返回创建失败信息msg result.addProperty(RestUtils.RESP_KEY_RC, RestUtils.RESP_RC_SUCC); result.addProperty(RestUtils.RESP_KEY_DATA, msg); diff --git a/contact-center/app/src/main/resources/static/js/CSKeFu_Admin.v1.js b/contact-center/app/src/main/resources/static/js/CSKeFu_Admin.v1.js index 553c3f9d..6d333315 100644 --- a/contact-center/app/src/main/resources/static/js/CSKeFu_Admin.v1.js +++ b/contact-center/app/src/main/resources/static/js/CSKeFu_Admin.v1.js @@ -56,5 +56,35 @@ function processUserAddOrUpdateResult(responsecode, cb){ layer.msg('用户编辑成功',{icon: 1, time: 1000}); cb(); break; + case 'billingquotaexception.no_license_found': + layer.msg('【使用授权证书】证书不存在,联系系统超级管理员导入。',{icon: 2, time: 5000}); + break; + case 'billingquotaexception.response_unexpected': + layer.msg('【使用授权证书】证书商店返回异常,稍后再试。',{icon: 2, time: 5000}); + break; + case 'billingquotaexception.invalid_request_body': + layer.msg('【使用授权证书】请求证书商店参数不合法,请获取最新软件代码。',{icon: 2, time: 5000}); + break; + case 'billingquotaexception.license_invalid': + layer.msg('【使用授权证书】证书商店中不存在该证书,请联系系统超级管理员导入。',{icon: 2, time: 5000}); + break; + case 'billingquotaexception.product_invalid': + layer.msg('【使用授权证书】产品或产品款式不存在,请联系系统超级管理员导入新证书。',{icon: 2, time: 5000}); + break; + case 'billingquotaexception.license_expired_or_exhausted': + layer.msg('【使用授权证书】证书过期或耗尽,请升级证书或绑定新证书。',{icon: 2, time: 5000}); + break; + case 'billingquotaexception.license_disabled_serverinst': + layer.msg('【使用授权证书】证书商店禁用了本服务实例。',{icon: 2, time: 5000}); + break; + case 'billingquotaexception.license_unsupport_refund': + layer.msg('【使用授权证书】目前使用的证书不支持回退配额。',{icon: 2, time: 5000}); + break; + case 'billingquotaexception.license_quota_inadequate': + layer.msg('【使用授权证书】本次操作需要使用的配额资源超过证书中剩余配额,请联系系统超级管理员升级证书。',{icon: 2, time: 5000}); + break; + case 'billingquotaexception.internal_error': + layer.msg('【使用授权证书】系统错误,稍后再试。',{icon: 2, time: 5000}); + break; } } \ No newline at end of file diff --git a/contact-center/app/src/main/resources/templates/admin/user/index.pug b/contact-center/app/src/main/resources/templates/admin/user/index.pug index c6641ab6..47ff38ea 100644 --- a/contact-center/app/src/main/resources/templates/admin/user/index.pug +++ b/contact-center/app/src/main/resources/templates/admin/user/index.pug @@ -121,6 +121,8 @@ block content layer.msg('新用户创建成功',{icon: 1, time: 1000}) else if (msg == 'edit_user_success') layer.msg('用户编辑成功', {icon: 1, time: 1000}) + else if (msg == 'billingquotaexception.no_license_found') + layer.msg('证书不存在,联系系统超级管理员导入授权使用证书',{icon: 2, time: 3000}) }); layui.use(['laypage', 'layer'], function(){ var laypage = layui.laypage