1
0
mirror of https://github.com/chatopera/cosin.git synced 2025-06-16 18:30:03 +08:00
Signed-off-by: Hai Liang Wang <hai@chatopera.com>
This commit is contained in:
Hai Liang Wang 2023-09-22 16:08:55 +08:00
parent f55785883a
commit 1cc39f5901
12 changed files with 281 additions and 79 deletions

View File

@ -220,15 +220,18 @@ public class Constants {
/**
* License
*/
public static final String PRODUCT_BASIC_ID = "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 SHORTID = "shortId";
public static final String LICENSES = "licenses";
public static final String ADDDATE = "addDate";
public static final String LICENSE = "license";
public static final String UPDATETIME = "updateTime";
public static final String STATUS = "status";
public static final String PRODUCT = "product";
public static final String LICENSESTOREPROVIDER = "licenseStoreProvider";
}

View File

@ -14,15 +14,19 @@
package com.cskefu.cc.config;
import com.chatopera.store.sdk.QuotaWdClient;
import com.chatopera.store.sdk.exceptions.InvalidProviderException;
import com.cskefu.cc.basic.MainContext;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class QuotaWdClientConfig {
@Value("${cskefu.license.store}")
private String cskefuLicenseStore;
@Value("${license.store.provider}")
private String licenseStoreProvider;
/**
* 证书商店服务客户端
@ -30,9 +34,13 @@ public class QuotaWdClientConfig {
* @return
*/
@Bean
public QuotaWdClient quotaWdClient() {
public QuotaWdClient quotaWdClient() throws InvalidProviderException {
if (StringUtils.isBlank(licenseStoreProvider)) {
System.out.println("[license] invalid license provider info, service is terminated.");
System.exit(1);
}
QuotaWdClient quotaWdClient = new QuotaWdClient();
quotaWdClient.setBaseUrl(cskefuLicenseStore);
return quotaWdClient;
}
}

View File

@ -10,9 +10,11 @@
*/
package com.cskefu.cc.controller.admin;
import com.chatopera.store.sdk.exceptions.InvalidRequestException;
import com.chatopera.store.sdk.exceptions.InvalidResponseException;
import com.cskefu.cc.basic.Constants;
import com.cskefu.cc.controller.Handler;
import com.cskefu.cc.exception.LicenseNotFoundException;
import com.cskefu.cc.exception.MetaKvInvalidKeyException;
import com.cskefu.cc.model.User;
import com.cskefu.cc.proxy.LicenseProxy;
@ -27,6 +29,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@ -46,15 +49,15 @@ public class LicenseCtrl extends Handler {
public ModelAndView index(ModelMap map, HttpServletRequest request) {
User user = super.getUser(request);
if (user.isSuperadmin()) {
try {
List<JSONObject> licenses = licenseProxy.getLicensesFromStore();
map.addAttribute(Constants.UPDATETIME, new Date());
map.addAttribute(Constants.LICENSES, licenses);
map.addAttribute(Constants.LICENSESTOREPROVIDER, licenseProxy.getLicenseStoreProvider());
} catch (InvalidResponseException e) {
throw new RuntimeException(e);
}
return request(super.createView("/admin/license/index"));
} else {
return request(super.createView("/public/error"));
@ -84,38 +87,64 @@ public class LicenseCtrl extends Handler {
@Menu(type = "admin", subtype = "license")
public ModelAndView save(ModelMap map,
HttpServletRequest request,
@Valid String licenseShortId) throws MetaKvInvalidKeyException {
@Valid String licenseShortId) throws MetaKvInvalidKeyException, InvalidRequestException {
User user = super.getUser(request);
logger.info("[save] licenseShortId {}", licenseShortId);
String msg = "";
if (user.isSuperadmin()) {
// 验证该证书不在当前证书列表中
JSONArray currents = licenseProxy.getLicensesInMetakv();
String msg = "";
boolean isAddedBefore = false;
for (int i = 0; i < currents.length(); i++) {
JSONObject item = (JSONObject) currents.get(i);
if (StringUtils.equals(item.getString("shortId"), licenseShortId)) {
isAddedBefore = true;
break;
}
}
if (isAddedBefore) {
msg = "already_added";
return request(super.createView(
"redirect:/admin/license/index.html?msg=" + msg));
}
// 验证该证书存在
try {
JSONObject licenseData = licenseProxy.getLicenseFromStore(licenseShortId);
JSONObject licenseKvData = new JSONObject();
licenseKvData.put(Constants.SHORTID, licenseData.getJSONObject(Constants.LICENSE).getString(Constants.SHORTID));
licenseKvData.put(Constants.ADDDATE, new Date());
/**
* 验证证书可以添加
*/
// 验证该证书不在当前证书列表中
JSONArray currents = licenseProxy.getLicensesInMetakv();
boolean isAddedBefore = false;
// 添加该证书
for (int i = 0; i < currents.length(); i++) {
JSONObject item = (JSONObject) currents.get(i);
if (StringUtils.equals(item.getString(Constants.SHORTID), licenseShortId)) {
isAddedBefore = true;
break;
}
}
if (isAddedBefore) {
msg = "already_added";
return request(super.createView(
"redirect:/admin/license/index.html?msg=" + msg));
}
// 验证该证书存在
licenseProxy.existLicenseInStore(licenseShortId);
// 验证该证书的所属产品没有现在没有其它证书同一个产品最多只有一个证书
JSONObject licBasic = licenseProxy.getLicenseBasicsFromStore(licenseShortId);
final String productId = licBasic.getJSONObject(Constants.PRODUCT).getString(Constants.SHORTID);
boolean isProductAdded = false;
JSONArray addedLicenseBasicsFromStore = licenseProxy.getAddedLicenseBasicsFromStore();
for (int i = 0; i < addedLicenseBasicsFromStore.length(); i++) {
JSONObject item = (JSONObject) addedLicenseBasicsFromStore.get(i);
if (StringUtils.equals(item.getJSONObject(Constants.PRODUCT).getString(Constants.SHORTID), productId)) {
isProductAdded = true;
break;
}
}
if (isProductAdded) {
msg = "product_added_already";
return request(super.createView(
"redirect:/admin/license/index.html?msg=" + msg));
}
/**
* 添加该证书
*/
JSONObject licenseKvData = new JSONObject();
licenseKvData.put(Constants.SHORTID, licenseShortId);
licenseKvData.put(Constants.ADDDATE, new Date());
currents.put(0, licenseKvData);
licenseProxy.createOrUpdateMetaKv(Constants.LICENSEIDS, currents.toString(), Constants.METAKV_DATATYPE_STRING);
@ -123,6 +152,55 @@ public class LicenseCtrl extends Handler {
List<JSONObject> licenses = licenseProxy.getLicensesFromStore();
map.addAttribute(Constants.LICENSES, licenses);
map.addAttribute("updateTime", new Date());
map.addAttribute(Constants.LICENSESTOREPROVIDER, licenseProxy.getLicenseStoreProvider());
return request(super.createView("/admin/license/index"));
} catch (InvalidResponseException e) {
logger.warn("[save] error in getLicenseFromStore", e);
msg = "invalid_id";
return request(super.createView(
"redirect:/admin/license/index.html?msg=" + msg));
} catch (LicenseNotFoundException e) {
logger.warn("[save] error in getLicenseFromStore", e);
msg = "notfound_id";
return request(super.createView(
"redirect:/admin/license/index.html?msg=" + msg));
}
} else {
return request(super.createView("/public/error"));
}
}
@RequestMapping("/delete/{licenseShortId}")
@Menu(type = "admin", subtype = "license")
public ModelAndView delete(ModelMap map,
HttpServletRequest request,
@PathVariable String licenseShortId) throws MetaKvInvalidKeyException {
User user = super.getUser(request);
logger.info("[delete] licenseShortId {}", licenseShortId);
String msg = "";
if (user.isSuperadmin()) {
try {
JSONArray currents = licenseProxy.getLicensesInMetakv();
JSONArray post = new JSONArray();
for (int i = 0; i < currents.length(); i++) {
JSONObject item = (JSONObject) currents.get(i);
if (!StringUtils.equals(item.getString(Constants.SHORTID), licenseShortId)) {
post.put(item);
}
}
/**
* 添加该证书
*/
licenseProxy.createOrUpdateMetaKv(Constants.LICENSEIDS, post.toString(), Constants.METAKV_DATATYPE_STRING);
// 跳转回到证书列表
List<JSONObject> licenses = licenseProxy.getLicensesFromStore();
map.addAttribute(Constants.LICENSES, licenses);
map.addAttribute("updateTime", new Date());
map.addAttribute(Constants.LICENSESTOREPROVIDER, licenseProxy.getLicenseStoreProvider());
return request(super.createView("/admin/license/index"));
} catch (InvalidResponseException e) {
@ -131,10 +209,9 @@ public class LicenseCtrl extends Handler {
return request(super.createView(
"redirect:/admin/license/index.html?msg=" + msg));
}
} else {
return request(super.createView("/public/error"));
}
}
}

View File

@ -0,0 +1,20 @@
/*
* Copyright (C) 2023 Beijing Huaxia Chunsong Technology Co., Ltd.
* <https://www.chatopera.com>, 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, <https://www.chatopera.com>,
* Licensed under the Apache License, Version 2.0,
* http://www.apache.org/licenses/LICENSE-2.0
*/
package com.cskefu.cc.exception;
public class LicenseNotFoundException extends Exception{
public LicenseNotFoundException(final String s){
super(s);
}
}

View File

@ -10,12 +10,15 @@
*/
package com.cskefu.cc.proxy;
import com.chatopera.store.enums.LICSTATUS;
import com.chatopera.store.sdk.QuotaWdClient;
import com.chatopera.store.sdk.Response;
import com.chatopera.store.sdk.exceptions.InvalidRequestException;
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.model.MetaKv;
@ -188,7 +191,12 @@ public class LicenseProxy {
addDates.put(obj.getString(Constants.SHORTID), obj.getString(Constants.ADDDATE));
}
Response resp = quotaWdClient.getLicensesInfo(licenseIds);
Response resp = null;
try {
resp = quotaWdClient.getLicenseBasics(licenseIds);
} catch (InvalidRequestException e) {
return result;
}
JSONArray data = (JSONArray) resp.getData();
for (int i = 0; i < data.length(); i++) {
@ -232,8 +240,8 @@ public class LicenseProxy {
* @return
* @throws InvalidResponseException
*/
public JSONObject getLicenseFromStore(final String licenseShortId) throws InvalidResponseException {
Response resp = quotaWdClient.getLicenseInfo(licenseShortId);
public JSONObject getLicenseBasicsFromStore(final String licenseShortId) throws InvalidResponseException, InvalidRequestException {
Response resp = quotaWdClient.getLicenseBasics(licenseShortId);
if (resp.getRc() == 0) {
JSONArray data = (JSONArray) resp.getData();
if (data.length() != 1)
@ -246,4 +254,64 @@ public class LicenseProxy {
}
/**
* 获得已经添加的证书在 Store 中的基本信息
*
* @return
* @throws InvalidResponseException
*/
public JSONArray getAddedLicenseBasicsFromStore() throws InvalidResponseException {
JSONArray arr = getLicensesInMetakv();
List<String> ids = new ArrayList<>();
for (int i = 0; i < arr.length(); i++) {
ids.add(((JSONObject) arr.get(i)).getString(Constants.SHORTID));
}
if (ids.size() > 0) {
Response resp = null;
try {
resp = quotaWdClient.getLicenseBasics(StringUtils.join(ids, ","));
} catch (InvalidRequestException e) {
logger.error("[getAddedLicenseBasicsFromStore] InvalidRequestException", e);
}
if (resp.getRc() != 0) {
throw new InvalidResponseException("Invalid response, rc " + Integer.toString(resp.getRc()));
}
return (JSONArray) resp.getData();
} else {
logger.error("[license] getAddedLicenseBasicsFromStore - No license ids in metaKv");
return new JSONArray();
}
}
/**
* 验证证书存在
*
* @param licenseShortId
* @return
*/
public LICSTATUS existLicenseInStore(final String licenseShortId) throws InvalidResponseException, LicenseNotFoundException, InvalidRequestException {
Map<String, LICSTATUS> statuses = quotaWdClient.getLicenseStatus(licenseShortId);
if (statuses.size() == 1) {
for (final Map.Entry<String, LICSTATUS> entry : statuses.entrySet()) {
final LICSTATUS status = entry.getValue();
if (status == LICSTATUS.NOTFOUND)
throw new LicenseNotFoundException("LicenseId not found [" + licenseShortId + "]");
return status;
}
throw new InvalidResponseException("Unexpected response, internal error.");
} else {
throw new InvalidResponseException("Unexpected response, should contain one record.");
}
}
public String getLicenseStoreProvider() {
return quotaWdClient.getBaseUrl();
}
}

View File

@ -76,6 +76,7 @@ spring.datasource.password=123456
spring.jpa.properties.hibernate.current_session_context_class=org.springframework.orm.hibernate5.SpringSessionContext
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.allow_update_outside_transaction=true
##############################################
# Cache
@ -152,15 +153,19 @@ cskefu.modules.cca=true
cskefu.modules.entim=false
cskefu.modules.report=true
# https://gitlab.chatopera.com/chatopera/cosinee/issues/838
cskefu.settings.webim.visitor-separate=false
# License Service Provider URL
cskefu.license.store=https://store.chatopera.com
##############################################
# Skype Channel
# Channels
##############################################
channel.skype.crm=
# https://gitlab.chatopera.com/chatopera/cosinee/issues/838
cskefu.settings.webim.visitor-separate=false
##############################################
# License
##############################################
# License Service Provider URL
license.store.provider=https://store.chatopera.com
##############################################
# Telemetry
@ -173,14 +178,4 @@ telemetry.channel.webim.visitor=on
extras.login.banner=off
extras.login.chatbox=off
extras.auth.super-admin.pass=
extras.log.request=off
spring.jpa.properties.hibernate.allow_update_outside_transaction=true
##############################################
# ssl
##############################################
# server.ssl.key-store=classpath:cskefu.jks
# server.ssl.key-alias=cskefu
# server.ssl.key-store-password=123456
# server.http2.enabled=true
extras.log.request=off

View File

@ -312,6 +312,21 @@ var arrayListPrototype = {
ArrayList.prototype = arrayListPrototype;
/**
* 复制值到系统粘贴板
* https://www.freecodecamp.org/news/copy-text-to-clipboard-javascript/
* @param val
*/
function copyValue2ClipboardOnOS(val, cb) {
navigator.clipboard.writeText(val).then(() => {
/* Resolved - text copied to clipboard successfully */
if(cb && typeof cb === 'function') cb();
},(err) => {
/* Rejected - text failed to copy to the clipboard */
if(cb && typeof cb === 'function') cb(err || "Fail");
});
}
/*!
Math.uuid.js (v1.4)

View File

@ -26,6 +26,7 @@ html(xmlns='http://www.w3.org/1999/xhtml', xmlns:th='http://www.thymeleaf.org',
link(rel='stylesheet', href='/css/layui.css')
link(rel='stylesheet', href='/res/css.html')
link(rel='stylesheet', href='/css/flexboxgrid.min.css')
script(src='/js/utils.js')
script(src='/js/jquery-1.10.2.min.js')
script(src='/js/jquery.form.js')
script(src='/js/ztree/jquery.ztree.all.min.js')

View File

@ -10,7 +10,7 @@
form.layui-form(action='/admin/license/save.html', method='post')
.layui-form-item(style='margin-top:10px;')
.layui-inline
label.layui-form-label(style='width:150px;') 证书标识:
label.layui-form-label(style='width:150px;') 证书标识:
.layui-input-inline
input.layui-input(type='text', name='licenseShortId', required, lay-verify='required', autocomplete='off')
.layui-form-mid.layui-word-aux

View File

@ -13,8 +13,14 @@ block content
| 使用授权证书列表 (#{licenses.size()})
| ,更新时间 #{pugHelper.formatDate('yyyy-MM-dd HH:mm:ss', updateTime)}
span(style='float:right;')
button.layui-btn.layui-btn-small.green(href='/admin/license/add.html', data-toggle='ajax', data-width='550', data-height='450', data-title='添加使用授权证书')
| 添加使用授权证书
.layui-btn-group.ukefu-btn-group
button.layui-btn.layui-btn-small(href='/admin/license/add.html', data-toggle='ajax', data-width='550', data-height='450', data-title='添加使用授权证书')
i.layui-icon &#xe654;
| 导入
button.layui-btn.layui-btn-small(onclick='location.reload()')
span 刷新
button.layui-btn.layui-btn-small(onclick='openLicenseStorePage()')
span 打开证书商店
.row(style='padding:5px;')
.col-lg-12
@ -24,13 +30,13 @@ block content
col(width='10%')
col(width='15%')
col(width='10%')
col(width='15%')
col(width='10%')
col(width='10%')
col(width='1%')
col(width='10%')
col(width='10%')
thead
tr
th 证书标识
th 证书 ID
th 产品标识
th 产品名称
th 有效期截止
@ -41,7 +47,8 @@ block content
tbody
for item in licenses
tr
td= pugHelper.messupStringWithStars(item.license.shortId)
- var messupLicenseShortId = pugHelper.messupStringWithStars(item.license.shortId)
td= messupLicenseShortId
td= item.product.shortId
td= item.product.name
td= item.license.effectivedateend
@ -49,30 +56,38 @@ block content
td= item.user.nickname
td= pugHelper.formatDate('yyyy-MM-dd HH:mm:ss', item.addDate)
td(style="white-space:nowrap;" nowrap="nowrap")
a(href="/admin/license/edit.html?id=" + item.license.shortId, data-toggle="ajax", data-width="550", data-height="450", data-title="编辑使用授权证书")
a(href="#", onclick="copyLicenseId2ClipboardOnOS('" + item.license.shortId + "');return false;")
i.layui-icon &#xe642;
span 编辑
a(href="/admin/license/delete.html?id=" + item.license.shortId style="margin-left:10px;" data-toggle="tip" title="请确认是否删除使用授权证书?")
span 复制 ID
a(href="/admin/license/delete/" + item.license.shortId + ".html" style="margin-left:10px;" data-toggle="tip" title="请确认是否删除使用授权证书 " + messupLicenseShortId + "")
i.layui-icon(style="color:red;") &#x1006;
span 删除
.row(style='padding:5px;')
.col-lg-12#page(style='text-align:center;')
script.
var msg = '#{msg}';
if (msg == 'already_added')
top.layer.alert('已经添加,不需要再次执行。', {icon: 1});
else if (msg == 'product_added_already')
top.layer.alert('同产品证书已经添加,不支持继续添加。', {icon: 2});
else if (msg == 'invalid_id')
top.layer.alert('不合法的证书标识', {icon: 2});
else if (msg == 'notfound_id')
top.layer.alert('不存在该证书信息', {icon: 2});
layui.use(['laypage', 'layer'], function () {
var laypage = layui.laypage
, layer = layui.layer;
// laypage({
// cont: 'page'
// , pages: #{emailList.totalPages} //总页数
// , curr: #{emailList.number + 1}
// , groups: 5 //连续显示分页数
// , jump: function (data, first) {
// if (!first) {
// location.href = "/admin/email/index.html?p=" + data.curr;
// }
// }
// });
});
function copyLicenseId2ClipboardOnOS(val){
copyValue2ClipboardOnOS(val, (err) => {
top.layer.msg('复制完成', {icon: 1, time: 2000, offset: 't'});
})
}
function openLicenseStorePage() {
var licenseStoreProvider = "#{licenseStoreProvider}";
window.open(licenseStoreProvider, "_blank");
}

View File

@ -42,7 +42,7 @@ services:
- BOT_THRESHOLD_FAQ_BEST_REPLY=${BOT_THRESHOLD_FAQ_BEST_REPLY:-0.9}
- BOT_THRESHOLD_FAQ_SUGG_REPLY=${BOT_THRESHOLD_FAQ_SUGG_REPLY:-0.1}
- CSKEFU_SETTINGS_WEBIM_VISITOR_SEPARATE=true
- CSKEFU_LICENSE_STORE=${CSKEFU_LICENSE_STORE:-https://store.chatopera.com}
- LICENSE_STORE_PROVIDER=${LICENSE_STORE_PROVIDER:-https://store.chatopera.com}
- TONGJI_BAIDU_SITEKEY=${TONGJI_BAIDU_SITEKEY:-placeholder}
- EXTRAS_LOGIN_BANNER=${NOTICE_LOGIN_BANNER:-off}
- EXTRAS_LOGIN_CHATBOX=${EXTRAS_LOGIN_CHATBOX:-off}

View File

@ -30,7 +30,7 @@ CACHE_SETUP_STRATEGY=create_by_force
BOT_THRESHOLD_FAQ_BEST_REPLY=0.8
BOT_THRESHOLD_FAQ_SUGG_REPLY=0.6
CSKEFU_LICENSE_STORE=https://store.chatopera.com
LICENSE_STORE_PROVIDER=https://store.chatopera.com
TONGJI_BAIDU_SITEKEY=placeholder
EXTRAS_LOGIN_BANNER=""
EXTRAS_LOGIN_CHATBOX=