diff --git a/README.md b/README.md index 9c6d1aa241..e594de40f6 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ * 联系:270580156@qq.com * Copyright (c) 2024 by bytedesk.com, All Rights Reserved. --> -# Bytedesk - Chat as a Service +# Bytedesk - Chat as a Service AI powered Omnichannel customer service With Team Cooperation diff --git a/deploy/server/agenticflow/assets/bpmn-BXGSTQk_.ttf b/deploy/server/agenticflow/assets/bpmn-BXGSTQk_.ttf new file mode 100644 index 0000000000..69b63bbf20 Binary files /dev/null and b/deploy/server/agenticflow/assets/bpmn-BXGSTQk_.ttf differ diff --git a/deploy/server/agenticflow/assets/bpmn-CCmvRa3L.woff b/deploy/server/agenticflow/assets/bpmn-CCmvRa3L.woff new file mode 100644 index 0000000000..64b6b03357 Binary files /dev/null and b/deploy/server/agenticflow/assets/bpmn-CCmvRa3L.woff differ diff --git a/deploy/server/agenticflow/assets/bpmn-CfAG4AR5.svg b/deploy/server/agenticflow/assets/bpmn-CfAG4AR5.svg new file mode 100644 index 0000000000..c2d5f357a3 --- /dev/null +++ b/deploy/server/agenticflow/assets/bpmn-CfAG4AR5.svg @@ -0,0 +1,224 @@ + + + +camunda Services GmbH + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/deploy/server/agenticflow/assets/bpmn-GG2Gc6GC.eot b/deploy/server/agenticflow/assets/bpmn-GG2Gc6GC.eot new file mode 100644 index 0000000000..2bb271eaa8 Binary files /dev/null and b/deploy/server/agenticflow/assets/bpmn-GG2Gc6GC.eot differ diff --git a/deploy/server/agenticflow/assets/bpmn-sIjfRMkI.woff2 b/deploy/server/agenticflow/assets/bpmn-sIjfRMkI.woff2 new file mode 100644 index 0000000000..52ef1261d3 Binary files /dev/null and b/deploy/server/agenticflow/assets/bpmn-sIjfRMkI.woff2 differ diff --git a/deploy/server/agenticflow/assets/components/adaptable-action-card/0.1.7/index.js b/deploy/server/agenticflow/assets/components/adaptable-action-card/0.1.7/index.js new file mode 100644 index 0000000000..eb6c0ab5f7 --- /dev/null +++ b/deploy/server/agenticflow/assets/components/adaptable-action-card/0.1.7/index.js @@ -0,0 +1 @@ +!function (t, n) { "object" == typeof exports && "object" == typeof module ? module.exports = n(require("React"), require("ChatUI")) : "function" == typeof define && define.amd ? define(["React", "ChatUI"], n) : "object" == typeof exports ? exports.AlimeComponentAdaptableActionCard = n(require("React"), require("ChatUI")) : t.AlimeComponentAdaptableActionCard = n(t.React, t.ChatUI) }(window, (function (t, n) { return function (t) { var n = {}; function e(r) { if (n[r]) return n[r].exports; var o = n[r] = { i: r, l: !1, exports: {} }; return t[r].call(o.exports, o, o.exports, e), o.l = !0, o.exports } return e.m = t, e.c = n, e.d = function (t, n, r) { e.o(t, n) || Object.defineProperty(t, n, { enumerable: !0, get: r }) }, e.r = function (t) { "undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(t, Symbol.toStringTag, { value: "Module" }), Object.defineProperty(t, "__esModule", { value: !0 }) }, e.t = function (t, n) { if (1 & n && (t = e(t)), 8 & n) return t; if (4 & n && "object" == typeof t && t && t.__esModule) return t; var r = Object.create(null); if (e.r(r), Object.defineProperty(r, "default", { enumerable: !0, value: t }), 2 & n && "string" != typeof t) for (var o in t) e.d(r, o, function (n) { return t[n] }.bind(null, o)); return r }, e.n = function (t) { var n = t && t.__esModule ? function () { return t.default } : function () { return t }; return e.d(n, "a", n), n }, e.o = function (t, n) { return Object.prototype.hasOwnProperty.call(t, n) }, e.p = "//dev.g.alicdn.com/alime-components/adaptable-action-card/0.1.7/", e(e.s = 19) }([function (n, e) { n.exports = t }, function (t, n, e) { (function (n) { var e = function (t) { return t && t.Math == Math && t }; t.exports = e("object" == typeof globalThis && globalThis) || e("object" == typeof window && window) || e("object" == typeof self && self) || e("object" == typeof n && n) || Function("return this")() }).call(this, e(25)) }, function (t, n) { t.exports = function (t) { try { return !!t() } catch (t) { return !0 } } }, function (t, e) { t.exports = n }, function (t, n, e) { var r = e(2); t.exports = !r((function () { return 7 != Object.defineProperty({}, 1, { get: function () { return 7 } })[1] })) }, function (t, n) { t.exports = function (t) { return "object" == typeof t ? null !== t : "function" == typeof t } }, function (t, n) { var e = {}.hasOwnProperty; t.exports = function (t, n) { return e.call(t, n) } }, function (t, n) { t.exports = {} }, function (t, n) { t.exports = function (t, n) { return { enumerable: !(1 & t), configurable: !(2 & t), writable: !(4 & t), value: n } } }, function (t, n, e) { var r = e(2), o = e(10), i = "".split; t.exports = r((function () { return !Object("z").propertyIsEnumerable(0) })) ? function (t) { return "String" == o(t) ? i.call(t, "") : Object(t) } : Object }, function (t, n) { var e = {}.toString; t.exports = function (t) { return e.call(t).slice(8, -1) } }, function (t, n) { t.exports = function (t) { if (null == t) throw TypeError("Can't call method on " + t); return t } }, function (t, n, e) { var r = e(5); t.exports = function (t, n) { if (!r(t)) return t; var e, o; if (n && "function" == typeof (e = t.toString) && !r(o = e.call(t))) return o; if ("function" == typeof (e = t.valueOf) && !r(o = e.call(t))) return o; if (!n && "function" == typeof (e = t.toString) && !r(o = e.call(t))) return o; throw TypeError("Can't convert object to primitive value") } }, function (t, n, e) { var r = e(4), o = e(2), i = e(29); t.exports = !r && !o((function () { return 7 != Object.defineProperty(i("div"), "a", { get: function () { return 7 } }).a })) }, function (t, n, e) { var r = e(31); t.exports = function (t, n, e) { if (r(t), void 0 === n) return t; switch (e) { case 0: return function () { return t.call(n) }; case 1: return function (e) { return t.call(n, e) }; case 2: return function (e, r) { return t.call(n, e, r) }; case 3: return function (e, r, o) { return t.call(n, e, r, o) } }return function () { return t.apply(n, arguments) } } }, function (t, n, e) { var r = e(4), o = e(32), i = e(8); t.exports = r ? function (t, n, e) { return o.f(t, n, i(1, e)) } : function (t, n, e) { return t[n] = e, t } }, function (t, n, e) { var r = e(1), o = e(40), i = e(6), c = e(44), a = e(17), u = e(45), f = o("wks"), s = r.Symbol, l = u ? s : s && s.withoutSetter || c; t.exports = function (t) { return i(f, t) || (a && i(s, t) ? f[t] = s[t] : f[t] = l("Symbol." + t)), f[t] } }, function (t, n, e) { var r = e(2); t.exports = !!Object.getOwnPropertySymbols && !r((function () { return !String(Symbol()) })) }, function (t, n, e) { t.exports = e(20) }, function (t, n, e) { "use strict"; e.r(n), e.d(n, "default", (function () { return u })); var r = e(18), o = e.n(r), i = e(0), c = e.n(i), a = e(3); e(52); function u(t) { var n = t.data, e = t.ctx, r = t.meta; function u(t, n) { var o, i = t.action, c = t.param; "openWindow" === i ? e.util.openWindow(c.url) : "sendText" === i ? e.postMessage({ type: "text", content: { text: c.text }, context: r.context }) : "telephone" === i ? e.util.openWindow("tel:".concat(c.tel)) : "wangwang" === i && e.JSBridge.openWangxin({ nick: c.nick }), o = { c: "adaptable-action-card", d: "btn", text: t.param.text, position: n }, e.log.click(o, r.logParams) } Object(i.useEffect)((function () { r.history || e.ui.scrollToEnd() }), []); var f = n.picUrl, s = n.title, l = n.content, p = n.actionList, d = n.actionOrientation; return c.a.createElement(a.Card, { className: "AdaptableActionCard", size: "xl", fluid: !0 }, f && c.a.createElement("img", { className: "AdaptableActionCard-img", src: f, onLoad: function () { r.history || e.ui.scrollToEnd() }, alt: "" }), s && c.a.createElement(a.CardTitle, null, s), l && c.a.createElement(a.CardText, null, c.a.createElement(a.RichText, { content: l })), p && "[]" !== p && c.a.createElement(a.CardActions, { direction: "0" === d ? "column" : null }, o()(p).call(p, (function (t, n) { return c.a.createElement(a.Button, { color: "primary" === t.style ? "primary" : null, onClick: function () { return u(t, n) }, key: n }, t.text) })))) } }, function (t, n, e) { var r = e(21); t.exports = r }, function (t, n, e) { var r = e(22), o = Array.prototype; t.exports = function (t) { var n = t.map; return t === o || t instanceof Array && n === o.map ? r : n } }, function (t, n, e) { e(23); var r = e(51); t.exports = r("Array").map }, function (t, n, e) { "use strict"; var r = e(24), o = e(34).map, i = e(46), c = e(50), a = i("map"), u = c("map"); r({ target: "Array", proto: !0, forced: !a || !u }, { map: function (t) { return o(this, t, arguments.length > 1 ? arguments[1] : void 0) } }) }, function (t, n, e) { "use strict"; var r = e(1), o = e(26).f, i = e(30), c = e(7), a = e(14), u = e(15), f = e(6), s = function (t) { var n = function (n, e, r) { if (this instanceof t) { switch (arguments.length) { case 0: return new t; case 1: return new t(n); case 2: return new t(n, e) }return new t(n, e, r) } return t.apply(this, arguments) }; return n.prototype = t.prototype, n }; t.exports = function (t, n) { var e, l, p, d, v, y, m, h, b = t.target, x = t.global, g = t.stat, w = t.proto, A = x ? r : g ? r[b] : (r[b] || {}).prototype, j = x ? c : c[b] || (c[b] = {}), C = j.prototype; for (p in n) e = !i(x ? p : b + (g ? "." : "#") + p, t.forced) && A && f(A, p), v = j[p], e && (y = t.noTargetGet ? (h = o(A, p)) && h.value : A[p]), d = e && y ? y : n[p], e && typeof v == typeof d || (m = t.bind && e ? a(d, r) : t.wrap && e ? s(d) : w && "function" == typeof d ? a(Function.call, d) : d, (t.sham || d && d.sham || v && v.sham) && u(m, "sham", !0), j[p] = m, w && (f(c, l = b + "Prototype") || u(c, l, {}), c[l][p] = d, t.real && C && !C[p] && u(C, p, d))) } }, function (t, n) { var e; e = function () { return this }(); try { e = e || new Function("return this")() } catch (t) { "object" == typeof window && (e = window) } t.exports = e }, function (t, n, e) { var r = e(4), o = e(27), i = e(8), c = e(28), a = e(12), u = e(6), f = e(13), s = Object.getOwnPropertyDescriptor; n.f = r ? s : function (t, n) { if (t = c(t), n = a(n, !0), f) try { return s(t, n) } catch (t) { } if (u(t, n)) return i(!o.f.call(t, n), t[n]) } }, function (t, n, e) { "use strict"; var r = {}.propertyIsEnumerable, o = Object.getOwnPropertyDescriptor, i = o && !r.call({ 1: 2 }, 1); n.f = i ? function (t) { var n = o(this, t); return !!n && n.enumerable } : r }, function (t, n, e) { var r = e(9), o = e(11); t.exports = function (t) { return r(o(t)) } }, function (t, n, e) { var r = e(1), o = e(5), i = r.document, c = o(i) && o(i.createElement); t.exports = function (t) { return c ? i.createElement(t) : {} } }, function (t, n, e) { var r = e(2), o = /#|\.prototype\./, i = function (t, n) { var e = a[c(t)]; return e == f || e != u && ("function" == typeof n ? r(n) : !!n) }, c = i.normalize = function (t) { return String(t).replace(o, ".").toLowerCase() }, a = i.data = {}, u = i.NATIVE = "N", f = i.POLYFILL = "P"; t.exports = i }, function (t, n) { t.exports = function (t) { if ("function" != typeof t) throw TypeError(String(t) + " is not a function"); return t } }, function (t, n, e) { var r = e(4), o = e(13), i = e(33), c = e(12), a = Object.defineProperty; n.f = r ? a : function (t, n, e) { if (i(t), n = c(n, !0), i(e), o) try { return a(t, n, e) } catch (t) { } if ("get" in e || "set" in e) throw TypeError("Accessors not supported"); return "value" in e && (t[n] = e.value), t } }, function (t, n, e) { var r = e(5); t.exports = function (t) { if (!r(t)) throw TypeError(String(t) + " is not an object"); return t } }, function (t, n, e) { var r = e(14), o = e(9), i = e(35), c = e(36), a = e(38), u = [].push, f = function (t) { var n = 1 == t, e = 2 == t, f = 3 == t, s = 4 == t, l = 6 == t, p = 5 == t || l; return function (d, v, y, m) { for (var h, b, x = i(d), g = o(x), w = r(v, y, 3), A = c(g.length), j = 0, C = m || a, S = n ? C(d, A) : e ? C(d, 0) : void 0; A > j; j++)if ((p || j in g) && (b = w(h = g[j], j, x), t)) if (n) S[j] = b; else if (b) switch (t) { case 3: return !0; case 5: return h; case 6: return j; case 2: u.call(S, h) } else if (s) return !1; return l ? -1 : f || s ? s : S } }; t.exports = { forEach: f(0), map: f(1), filter: f(2), some: f(3), every: f(4), find: f(5), findIndex: f(6) } }, function (t, n, e) { var r = e(11); t.exports = function (t) { return Object(r(t)) } }, function (t, n, e) { var r = e(37), o = Math.min; t.exports = function (t) { return t > 0 ? o(r(t), 9007199254740991) : 0 } }, function (t, n) { var e = Math.ceil, r = Math.floor; t.exports = function (t) { return isNaN(t = +t) ? 0 : (t > 0 ? r : e)(t) } }, function (t, n, e) { var r = e(5), o = e(39), i = e(16)("species"); t.exports = function (t, n) { var e; return o(t) && ("function" != typeof (e = t.constructor) || e !== Array && !o(e.prototype) ? r(e) && null === (e = e[i]) && (e = void 0) : e = void 0), new (void 0 === e ? Array : e)(0 === n ? 0 : n) } }, function (t, n, e) { var r = e(10); t.exports = Array.isArray || function (t) { return "Array" == r(t) } }, function (t, n, e) { var r = e(41), o = e(42); (t.exports = function (t, n) { return o[t] || (o[t] = void 0 !== n ? n : {}) })("versions", []).push({ version: "3.6.3", mode: r ? "pure" : "global", copyright: "© 2020 Denis Pushkarev (zloirock.ru)" }) }, function (t, n) { t.exports = !0 }, function (t, n, e) { var r = e(1), o = e(43), i = r["__core-js_shared__"] || o("__core-js_shared__", {}); t.exports = i }, function (t, n, e) { var r = e(1), o = e(15); t.exports = function (t, n) { try { o(r, t, n) } catch (e) { r[t] = n } return n } }, function (t, n) { var e = 0, r = Math.random(); t.exports = function (t) { return "Symbol(" + String(void 0 === t ? "" : t) + ")_" + (++e + r).toString(36) } }, function (t, n, e) { var r = e(17); t.exports = r && !Symbol.sham && "symbol" == typeof Symbol.iterator }, function (t, n, e) { var r = e(2), o = e(16), i = e(47), c = o("species"); t.exports = function (t) { return i >= 51 || !r((function () { var n = []; return (n.constructor = {})[c] = function () { return { foo: 1 } }, 1 !== n[t](Boolean).foo })) } }, function (t, n, e) { var r, o, i = e(1), c = e(48), a = i.process, u = a && a.versions, f = u && u.v8; f ? o = (r = f.split("."))[0] + r[1] : c && (!(r = c.match(/Edge\/(\d+)/)) || r[1] >= 74) && (r = c.match(/Chrome\/(\d+)/)) && (o = r[1]), t.exports = o && +o }, function (t, n, e) { var r = e(49); t.exports = r("navigator", "userAgent") || "" }, function (t, n, e) { var r = e(7), o = e(1), i = function (t) { return "function" == typeof t ? t : void 0 }; t.exports = function (t, n) { return arguments.length < 2 ? i(r[t]) || i(o[t]) : r[t] && r[t][n] || o[t] && o[t][n] } }, function (t, n, e) { var r = e(4), o = e(2), i = e(6), c = Object.defineProperty, a = function (t) { throw t }; t.exports = function (t, n) { n || (n = {}); var e = [][t], u = !!i(n, "ACCESSORS") && n.ACCESSORS, f = i(n, 0) ? n[0] : a, s = i(n, 1) ? n[1] : void 0; return !!e && !o((function () { if (u && !r) return !0; var t = { length: -1 }, n = function (n) { u ? c(t, n, { enumerable: !0, get: a }) : t[n] = 1 }; n(1), n(2147483646), n(4294967294), e.call(t, f, s) })) } }, function (t, n, e) { var r = e(7); t.exports = function (t) { return r[t + "Prototype"] } }, function (t, n, e) { var r = e(53), o = e(54); "string" == typeof (o = o.__esModule ? o.default : o) && (o = [[t.i, o, ""]]); var i = { insert: "head", singleton: !1 }, c = (r(t.i, o, i), o.locals ? o.locals : {}); t.exports = c }, function (t, n, e) { "use strict"; var r, o = function () { return void 0 === r && (r = Boolean(window && document && document.all && !window.atob)), r }, i = function () { var t = {}; return function (n) { if (void 0 === t[n]) { var e = document.querySelector(n); if (window.HTMLIFrameElement && e instanceof window.HTMLIFrameElement) try { e = e.contentDocument.head } catch (t) { e = null } t[n] = e } return t[n] } }(), c = {}; function a(t, n, e) { for (var r = 0; r < n.length; r++) { var o = { css: n[r][1], media: n[r][2], sourceMap: n[r][3] }; c[t][r] ? c[t][r](o) : c[t].push(y(o, e)) } } function u(t) { var n = document.createElement("style"), r = t.attributes || {}; if (void 0 === r.nonce) { var o = e.nc; o && (r.nonce = o) } if (Object.keys(r).forEach((function (t) { n.setAttribute(t, r[t]) })), "function" == typeof t.insert) t.insert(n); else { var c = i(t.insert || "head"); if (!c) throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid."); c.appendChild(n) } return n } var f, s = (f = [], function (t, n) { return f[t] = n, f.filter(Boolean).join("\n") }); function l(t, n, e, r) { var o = e ? "" : r.css; if (t.styleSheet) t.styleSheet.cssText = s(n, o); else { var i = document.createTextNode(o), c = t.childNodes; c[n] && t.removeChild(c[n]), c.length ? t.insertBefore(i, c[n]) : t.appendChild(i) } } function p(t, n, e) { var r = e.css, o = e.media, i = e.sourceMap; if (o ? t.setAttribute("media", o) : t.removeAttribute("media"), i && btoa && (r += "\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(i)))), " */")), t.styleSheet) t.styleSheet.cssText = r; else { for (; t.firstChild;)t.removeChild(t.firstChild); t.appendChild(document.createTextNode(r)) } } var d = null, v = 0; function y(t, n) { var e, r, o; if (n.singleton) { var i = v++; e = d || (d = u(n)), r = l.bind(null, e, i, !1), o = l.bind(null, e, i, !0) } else e = u(n), r = p.bind(null, e, n), o = function () { !function (t) { if (null === t.parentNode) return !1; t.parentNode.removeChild(t) }(e) }; return r(t), function (n) { if (n) { if (n.css === t.css && n.media === t.media && n.sourceMap === t.sourceMap) return; r(t = n) } else o() } } t.exports = function (t, n, e) { return (e = e || {}).singleton || "boolean" == typeof e.singleton || (e.singleton = o()), t = e.base ? t + e.base : t, n = n || [], c[t] || (c[t] = []), a(t, n, e), function (n) { if (n = n || [], "[object Array]" === Object.prototype.toString.call(n)) { c[t] || (c[t] = []), a(t, n, e); for (var r = n.length; r < c[t].length; r++)c[t][r](); c[t].length = n.length, 0 === c[t].length && delete c[t] } } } }, function (t, n, e) { (n = e(55)(!0)).push([t.i, ".AdaptableActionCard-img{display:block;width:100%}", "", { version: 3, sources: ["style.css"], names: [], mappings: "AAAA,yBACE,aAAc,CACd,UACF", file: "style.css", sourcesContent: [".AdaptableActionCard-img {\n display: block;\n width: 100%;\n}\n"] }]), t.exports = n }, function (t, n, e) { "use strict"; t.exports = function (t) { var n = []; return n.toString = function () { return this.map((function (n) { var e = function (t, n) { var e = t[1] || "", r = t[3]; if (!r) return e; if (n && "function" == typeof btoa) { var o = (c = r, a = btoa(unescape(encodeURIComponent(JSON.stringify(c)))), u = "sourceMappingURL=data:application/json;charset=utf-8;base64,".concat(a), "/*# ".concat(u, " */")), i = r.sources.map((function (t) { return "/*# sourceURL=".concat(r.sourceRoot || "").concat(t, " */") })); return [e].concat(i).concat([o]).join("\n") } var c, a, u; return [e].join("\n") }(n, t); return n[2] ? "@media ".concat(n[2], " {").concat(e, "}") : e })).join("") }, n.i = function (t, e) { "string" == typeof t && (t = [[null, t, ""]]); for (var r = 0; r < t.length; r++) { var o = [].concat(t[r]); e && (o[2] ? o[2] = "".concat(e, " and ").concat(o[2]) : o[2] = e), n.push(o) } }, n } }]) })); \ No newline at end of file diff --git a/deploy/server/agenticflow/assets/components/adaptable-action-card/alime-base.oss-cn-beijing-internal.aliyuncs.com1598856680567-2.png b/deploy/server/agenticflow/assets/components/adaptable-action-card/alime-base.oss-cn-beijing-internal.aliyuncs.com1598856680567-2.png new file mode 100644 index 0000000000..d80b8c4746 Binary files /dev/null and b/deploy/server/agenticflow/assets/components/adaptable-action-card/alime-base.oss-cn-beijing-internal.aliyuncs.com1598856680567-2.png differ diff --git a/deploy/server/agenticflow/assets/components/adaptable-action-card/readme.md b/deploy/server/agenticflow/assets/components/adaptable-action-card/readme.md new file mode 100644 index 0000000000..5b66922d02 --- /dev/null +++ b/deploy/server/agenticflow/assets/components/adaptable-action-card/readme.md @@ -0,0 +1,3 @@ +# 图文按钮卡片 + +- diff --git a/deploy/server/agenticflow/assets/components/firstmsg/0.0.1/index.js b/deploy/server/agenticflow/assets/components/firstmsg/0.0.1/index.js new file mode 100644 index 0000000000..45e2ff127d --- /dev/null +++ b/deploy/server/agenticflow/assets/components/firstmsg/0.0.1/index.js @@ -0,0 +1 @@ +!function (t, n) { "object" == typeof exports && "object" == typeof module ? module.exports = n(require("React"), require("ChatUI")) : "function" == typeof define && define.amd ? define(["React", "ChatUI"], n) : "object" == typeof exports ? exports.AlimeComponentFirstmsg = n(require("React"), require("ChatUI")) : t.AlimeComponentFirstmsg = n(t.React, t.ChatUI) }(window, (function (t, n) { return function (t) { var n = {}; function r(e) { if (n[e]) return n[e].exports; var o = n[e] = { i: e, l: !1, exports: {} }; return t[e].call(o.exports, o, o.exports, r), o.l = !0, o.exports } return r.m = t, r.c = n, r.d = function (t, n, e) { r.o(t, n) || Object.defineProperty(t, n, { enumerable: !0, get: e }) }, r.r = function (t) { "undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(t, Symbol.toStringTag, { value: "Module" }), Object.defineProperty(t, "__esModule", { value: !0 }) }, r.t = function (t, n) { if (1 & n && (t = r(t)), 8 & n) return t; if (4 & n && "object" == typeof t && t && t.__esModule) return t; var e = Object.create(null); if (r.r(e), Object.defineProperty(e, "default", { enumerable: !0, value: t }), 2 & n && "string" != typeof t) for (var o in t) r.d(e, o, function (n) { return t[n] }.bind(null, o)); return e }, r.n = function (t) { var n = t && t.__esModule ? function () { return t.default } : function () { return t }; return r.d(n, "a", n), n }, r.o = function (t, n) { return Object.prototype.hasOwnProperty.call(t, n) }, r.p = "//dev.g.alicdn.com/alime-components/firstmsg/0.0.1/", r(r.s = 91) }([function (t, n) { t.exports = function (t) { try { return !!t() } catch (t) { return !0 } } }, function (t, n, r) { var e = r(0); t.exports = !e((function () { return 7 != Object.defineProperty({}, 1, { get: function () { return 7 } })[1] })) }, function (t, n, r) { "use strict"; var e = r(3), o = r(23).f, i = r(121), c = r(5), u = r(70), a = r(8), f = r(4), s = function (t) { var n = function (n, r, e) { if (this instanceof t) { switch (arguments.length) { case 0: return new t; case 1: return new t(n); case 2: return new t(n, r) }return new t(n, r, e) } return t.apply(this, arguments) }; return n.prototype = t.prototype, n }; t.exports = function (t, n) { var r, p, l, v, y, d, h, g, b = t.target, m = t.global, x = t.stat, O = t.proto, S = m ? e : x ? e[b] : (e[b] || {}).prototype, j = m ? c : c[b] || (c[b] = {}), w = j.prototype; for (l in n) r = !i(m ? l : b + (x ? "." : "#") + l, t.forced) && S && f(S, l), y = j[l], r && (d = t.noTargetGet ? (g = o(S, l)) && g.value : S[l]), v = r && d ? d : n[l], r && typeof y == typeof v || (h = t.bind && r ? u(v, e) : t.wrap && r ? s(v) : O && "function" == typeof v ? u(Function.call, v) : v, (t.sham || v && v.sham || y && y.sham) && a(h, "sham", !0), j[l] = h, O && (f(c, p = b + "Prototype") || a(c, p, {}), c[p][l] = v, t.real && w && !w[l] && a(w, l, v))) } }, function (t, n, r) { (function (n) { var r = function (t) { return t && t.Math == Math && t }; t.exports = r("object" == typeof globalThis && globalThis) || r("object" == typeof window && window) || r("object" == typeof self && self) || r("object" == typeof n && n) || Function("return this")() }).call(this, r(53)) }, function (t, n) { var r = {}.hasOwnProperty; t.exports = function (t, n) { return r.call(t, n) } }, function (t, n) { t.exports = {} }, function (t, n, r) { var e = r(3), o = r(41), i = r(4), c = r(42), u = r(43), a = r(77), f = o("wks"), s = e.Symbol, p = a ? s : s && s.withoutSetter || c; t.exports = function (t) { return i(f, t) || (u && i(s, t) ? f[t] = s[t] : f[t] = p("Symbol." + t)), f[t] } }, function (t, n, r) { (function (n) { var r = function (t) { return t && t.Math == Math && t }; t.exports = r("object" == typeof globalThis && globalThis) || r("object" == typeof window && window) || r("object" == typeof self && self) || r("object" == typeof n && n) || Function("return this")() }).call(this, r(53)) }, function (t, n, r) { var e = r(1), o = r(11), i = r(16); t.exports = e ? function (t, n, r) { return o.f(t, n, i(1, r)) } : function (t, n, r) { return t[n] = r, t } }, function (t, n, r) { var e = r(36), o = r(67); t.exports = function (t) { return e(o(t)) } }, function (t, n) { t.exports = function (t) { return "object" == typeof t ? null !== t : "function" == typeof t } }, function (t, n, r) { var e = r(1), o = r(68), i = r(14), c = r(24), u = Object.defineProperty; n.f = e ? u : function (t, n, r) { if (i(t), n = c(n, !0), i(r), o) try { return u(t, n, r) } catch (t) { } if ("get" in r || "set" in r) throw TypeError("Accessors not supported"); return "value" in r && (t[n] = r.value), t } }, function (n, r) { n.exports = t }, function (t, n) { t.exports = function (t) { try { return !!t() } catch (t) { return !0 } } }, function (t, n, r) { var e = r(10); t.exports = function (t) { if (!e(t)) throw TypeError(String(t) + " is not an object"); return t } }, function (t, n) { var r = {}.hasOwnProperty; t.exports = function (t, n) { return r.call(t, n) } }, function (t, n) { t.exports = function (t, n) { return { enumerable: !(1 & t), configurable: !(2 & t), writable: !(4 & t), value: n } } }, function (t, n, r) { var e = r(67); t.exports = function (t) { return Object(e(t)) } }, function (t, r) { t.exports = n }, function (t, n, r) { var e = r(13); t.exports = !e((function () { return 7 != Object.defineProperty({}, 1, { get: function () { return 7 } })[1] })) }, function (t, n, r) { var e = r(56), o = r(57); t.exports = function (t) { return e(o(t)) } }, function (t, n) { t.exports = function (t) { return "object" == typeof t ? null !== t : "function" == typeof t } }, function (t, n, r) { var e = r(19), o = r(60), i = r(55); t.exports = e ? function (t, n, r) { return o.f(t, n, i(1, r)) } : function (t, n, r) { return t[n] = r, t } }, function (t, n, r) { var e = r(1), o = r(35), i = r(16), c = r(9), u = r(24), a = r(4), f = r(68), s = Object.getOwnPropertyDescriptor; n.f = e ? s : function (t, n) { if (t = c(t), n = u(n, !0), f) try { return s(t, n) } catch (t) { } if (a(t, n)) return i(!o.f.call(t, n), t[n]) } }, function (t, n, r) { var e = r(10); t.exports = function (t, n) { if (!e(t)) return t; var r, o; if (n && "function" == typeof (r = t.toString) && !e(o = r.call(t))) return o; if ("function" == typeof (r = t.valueOf) && !e(o = r.call(t))) return o; if (!n && "function" == typeof (r = t.toString) && !e(o = r.call(t))) return o; throw TypeError("Can't convert object to primitive value") } }, function (t, n, r) { var e = r(72), o = r(38); t.exports = Object.keys || function (t) { return e(t, o) } }, function (t, n) { t.exports = {} }, function (t, n, r) { var e = r(5), o = r(3), i = function (t) { return "function" == typeof t ? t : void 0 }; t.exports = function (t, n) { return arguments.length < 2 ? i(e[t]) || i(o[t]) : e[t] && e[t][n] || o[t] && o[t][n] } }, function (t, n, r) { var e = r(70), o = r(36), i = r(17), c = r(73), u = r(140), a = [].push, f = function (t) { var n = 1 == t, r = 2 == t, f = 3 == t, s = 4 == t, p = 6 == t, l = 5 == t || p; return function (v, y, d, h) { for (var g, b, m = i(v), x = o(m), O = e(y, d, 3), S = c(x.length), j = 0, w = h || u, P = n ? w(v, S) : r ? w(v, 0) : void 0; S > j; j++)if ((l || j in x) && (b = O(g = x[j], j, m), t)) if (n) P[j] = b; else if (b) switch (t) { case 3: return !0; case 5: return g; case 6: return j; case 2: a.call(P, g) } else if (s) return !1; return p ? -1 : f || s ? s : P } }; t.exports = { forEach: f(0), map: f(1), filter: f(2), some: f(3), every: f(4), find: f(5), findIndex: f(6) } }, function (t, n) { t.exports = !0 }, function (t, n, r) { var e = r(41), o = r(42), i = e("keys"); t.exports = function (t) { return i[t] || (i[t] = o(t)) } }, function (t, n) { t.exports = {} }, function (t, n, r) { t.exports = r(161) }, function (t, n, r) { t.exports = r(165) }, function (t, n, r) { var e = r(7), o = r(22); t.exports = function (t, n) { try { o(e, t, n) } catch (r) { e[t] = n } return n } }, function (t, n, r) { "use strict"; var e = {}.propertyIsEnumerable, o = Object.getOwnPropertyDescriptor, i = o && !e.call({ 1: 2 }, 1); n.f = i ? function (t) { var n = o(this, t); return !!n && n.enumerable } : e }, function (t, n, r) { var e = r(0), o = r(37), i = "".split; t.exports = e((function () { return !Object("z").propertyIsEnumerable(0) })) ? function (t) { return "String" == o(t) ? i.call(t, "") : Object(t) } : Object }, function (t, n) { var r = {}.toString; t.exports = function (t) { return r.call(t).slice(8, -1) } }, function (t, n) { t.exports = ["constructor", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "toLocaleString", "toString", "valueOf"] }, function (t, n, r) { var e = r(72), o = r(38).concat("length", "prototype"); n.f = Object.getOwnPropertyNames || function (t) { return e(t, o) } }, function (t, n) { n.f = Object.getOwnPropertySymbols }, function (t, n, r) { var e = r(29), o = r(76); (t.exports = function (t, n) { return o[t] || (o[t] = void 0 !== n ? n : {}) })("versions", []).push({ version: "3.6.4", mode: e ? "pure" : "global", copyright: "© 2020 Denis Pushkarev (zloirock.ru)" }) }, function (t, n) { var r = 0, e = Math.random(); t.exports = function (t) { return "Symbol(" + String(void 0 === t ? "" : t) + ")_" + (++r + e).toString(36) } }, function (t, n, r) { var e = r(0); t.exports = !!Object.getOwnPropertySymbols && !e((function () { return !String(Symbol()) })) }, function (t, n, r) { var e = r(1), o = r(0), i = r(4), c = Object.defineProperty, u = {}, a = function (t) { throw t }; t.exports = function (t, n) { if (i(u, t)) return u[t]; n || (n = {}); var r = [][t], f = !!i(n, "ACCESSORS") && n.ACCESSORS, s = i(n, 0) ? n[0] : a, p = i(n, 1) ? n[1] : void 0; return u[t] = !!r && !o((function () { if (f && !e) return !0; var t = { length: -1 }; f ? c(t, 1, { enumerable: !0, get: a }) : t[1] = 1, r.call(t, s, p) })) } }, function (t, n, r) { var e = r(5); t.exports = function (t) { return e[t + "Prototype"] } }, function (t, n, r) { var e = r(47), o = r(11).f, i = r(8), c = r(4), u = r(150), a = r(6)("toStringTag"); t.exports = function (t, n, r, f) { if (t) { var s = r ? t : t.prototype; c(s, a) || o(s, a, { configurable: !0, value: n }), f && !e && i(s, "toString", u) } } }, function (t, n, r) { var e = {}; e[r(6)("toStringTag")] = "z", t.exports = "[object z]" === String(e) }, function (t, n, r) { var e = r(47), o = r(37), i = r(6)("toStringTag"), c = "Arguments" == o(function () { return arguments }()); t.exports = e ? o : function (t) { var n, r, e; return void 0 === t ? "Undefined" : null === t ? "Null" : "string" == typeof (r = function (t, n) { try { return t[n] } catch (t) { } }(n = Object(t), i)) ? r : c ? o(n) : "Object" == (e = o(n)) && "function" == typeof n.callee ? "Arguments" : e } }, function (t, n, r) { t.exports = r(128) }, function (t, n, r) { t.exports = r(133) }, function (t, n, r) { t.exports = r(144) }, function (t, n, r) { var e = r(7), o = r(54).f, i = r(22), c = r(96), u = r(34), a = r(103), f = r(114); t.exports = function (t, n) { var r, s, p, l, v, y = t.target, d = t.global, h = t.stat; if (r = d ? e : h ? e[y] || u(y, {}) : (e[y] || {}).prototype) for (s in n) { if (l = n[s], p = t.noTargetGet ? (v = o(r, s)) && v.value : r[s], !f(d ? s : y + (h ? "." : "#") + s, t.forced) && void 0 !== p) { if (typeof l == typeof p) continue; a(l, p) } (t.sham || p && p.sham) && i(l, "sham", !0), c(r, s, l, t) } } }, function (t, n) { var r; r = function () { return this }(); try { r = r || new Function("return this")() } catch (t) { "object" == typeof window && (r = window) } t.exports = r }, function (t, n, r) { var e = r(19), o = r(93), i = r(55), c = r(20), u = r(58), a = r(15), f = r(59), s = Object.getOwnPropertyDescriptor; n.f = e ? s : function (t, n) { if (t = c(t), n = u(n, !0), f) try { return s(t, n) } catch (t) { } if (a(t, n)) return i(!o.f.call(t, n), t[n]) } }, function (t, n) { t.exports = function (t, n) { return { enumerable: !(1 & t), configurable: !(2 & t), writable: !(4 & t), value: n } } }, function (t, n, r) { var e = r(13), o = r(94), i = "".split; t.exports = e((function () { return !Object("z").propertyIsEnumerable(0) })) ? function (t) { return "String" == o(t) ? i.call(t, "") : Object(t) } : Object }, function (t, n) { t.exports = function (t) { if (null == t) throw TypeError("Can't call method on " + t); return t } }, function (t, n, r) { var e = r(21); t.exports = function (t, n) { if (!e(t)) return t; var r, o; if (n && "function" == typeof (r = t.toString) && !e(o = r.call(t))) return o; if ("function" == typeof (r = t.valueOf) && !e(o = r.call(t))) return o; if (!n && "function" == typeof (r = t.toString) && !e(o = r.call(t))) return o; throw TypeError("Can't convert object to primitive value") } }, function (t, n, r) { var e = r(19), o = r(13), i = r(95); t.exports = !e && !o((function () { return 7 != Object.defineProperty(i("div"), "a", { get: function () { return 7 } }).a })) }, function (t, n, r) { var e = r(19), o = r(59), i = r(61), c = r(58), u = Object.defineProperty; n.f = e ? u : function (t, n, r) { if (i(t), n = c(n, !0), i(r), o) try { return u(t, n, r) } catch (t) { } if ("get" in r || "set" in r) throw TypeError("Accessors not supported"); return "value" in r && (t[n] = r.value), t } }, function (t, n, r) { var e = r(21); t.exports = function (t) { if (!e(t)) throw TypeError(String(t) + " is not an object"); return t } }, function (t, n, r) { var e = r(63), o = Function.toString; "function" != typeof e.inspectSource && (e.inspectSource = function (t) { return o.call(t) }), t.exports = e.inspectSource }, function (t, n, r) { var e = r(7), o = r(34), i = e["__core-js_shared__"] || o("__core-js_shared__", {}); t.exports = i }, function (t, n) { t.exports = {} }, function (t, n) { var r = Math.ceil, e = Math.floor; t.exports = function (t) { return isNaN(t = +t) ? 0 : (t > 0 ? e : r)(t) } }, function (t, n, r) { r(120); var e = r(5).Object, o = t.exports = function (t, n, r) { return e.defineProperty(t, n, r) }; e.defineProperty.sham && (o.sham = !0) }, function (t, n) { t.exports = function (t) { if (null == t) throw TypeError("Can't call method on " + t); return t } }, function (t, n, r) { var e = r(1), o = r(0), i = r(69); t.exports = !e && !o((function () { return 7 != Object.defineProperty(i("div"), "a", { get: function () { return 7 } }).a })) }, function (t, n, r) { var e = r(3), o = r(10), i = e.document, c = o(i) && o(i.createElement); t.exports = function (t) { return c ? i.createElement(t) : {} } }, function (t, n, r) { var e = r(122); t.exports = function (t, n, r) { if (e(t), void 0 === n) return t; switch (r) { case 0: return function () { return t.call(n) }; case 1: return function (r) { return t.call(n, r) }; case 2: return function (r, e) { return t.call(n, r, e) }; case 3: return function (r, e, o) { return t.call(n, r, e, o) } }return function () { return t.apply(n, arguments) } } }, function (t, n, r) { var e = r(1), o = r(11), i = r(14), c = r(25); t.exports = e ? Object.defineProperties : function (t, n) { i(t); for (var r, e = c(n), u = e.length, a = 0; u > a;)o.f(t, r = e[a++], n[r]); return t } }, function (t, n, r) { var e = r(4), o = r(9), i = r(126).indexOf, c = r(26); t.exports = function (t, n) { var r, u = o(t), a = 0, f = []; for (r in u) !e(c, r) && e(u, r) && f.push(r); for (; n.length > a;)e(u, r = n[a++]) && (~i(f, r) || f.push(r)); return f } }, function (t, n, r) { var e = r(74), o = Math.min; t.exports = function (t) { return t > 0 ? o(e(t), 9007199254740991) : 0 } }, function (t, n) { var r = Math.ceil, e = Math.floor; t.exports = function (t) { return isNaN(t = +t) ? 0 : (t > 0 ? e : r)(t) } }, function (t, n, r) { var e = r(37); t.exports = Array.isArray || function (t) { return "Array" == e(t) } }, function (t, n, r) { var e = r(3), o = r(141), i = e["__core-js_shared__"] || o("__core-js_shared__", {}); t.exports = i }, function (t, n, r) { var e = r(43); t.exports = e && !Symbol.sham && "symbol" == typeof Symbol.iterator }, function (t, n, r) { var e = r(0), o = r(6), i = r(142), c = o("species"); t.exports = function (t) { return i >= 51 || !e((function () { var n = []; return (n.constructor = {})[c] = function () { return { foo: 1 } }, 1 !== n[t](Boolean).foo })) } }, function (t, n, r) { var e, o = r(14), i = r(71), c = r(38), u = r(26), a = r(147), f = r(69), s = r(30), p = s("IE_PROTO"), l = function () { }, v = function (t) { return " + + + + +
+ + + + + + + diff --git a/deploy/server/agenticflow/logo.png b/deploy/server/agenticflow/logo.png new file mode 100644 index 0000000000..b90dc20264 Binary files /dev/null and b/deploy/server/agenticflow/logo.png differ diff --git a/deploy/server/agenticflow/logo_b.png b/deploy/server/agenticflow/logo_b.png new file mode 100644 index 0000000000..4d617f0e91 Binary files /dev/null and b/deploy/server/agenticflow/logo_b.png differ diff --git a/deploy/server/agenticflow/logo_bb.png b/deploy/server/agenticflow/logo_bb.png new file mode 100644 index 0000000000..b90dc20264 Binary files /dev/null and b/deploy/server/agenticflow/logo_bb.png differ diff --git a/deploy/server/agenticflow/logo_round.png b/deploy/server/agenticflow/logo_round.png new file mode 100644 index 0000000000..80d3028d26 Binary files /dev/null and b/deploy/server/agenticflow/logo_round.png differ diff --git a/deploy/server/agenticflow/node.svg b/deploy/server/agenticflow/node.svg new file mode 100644 index 0000000000..38d4eaaff4 --- /dev/null +++ b/deploy/server/agenticflow/node.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/deploy/server/agenticflow/readme.md b/deploy/server/agenticflow/readme.md new file mode 100644 index 0000000000..a44120dc9c --- /dev/null +++ b/deploy/server/agenticflow/readme.md @@ -0,0 +1,3 @@ +# README + +- 修改 config.json 文件,设置后端连接 diff --git a/modules/ai/src/main/java/com/bytedesk/ai/provider/LlmProviderRestController.java b/modules/ai/src/main/java/com/bytedesk/ai/provider/LlmProviderRestController.java index eccb83b98f..1c550154e5 100644 --- a/modules/ai/src/main/java/com/bytedesk/ai/provider/LlmProviderRestController.java +++ b/modules/ai/src/main/java/com/bytedesk/ai/provider/LlmProviderRestController.java @@ -120,7 +120,6 @@ public class LlmProviderRestController extends BaseRestController sendSyncApiCommand(Channel channel, String command, String arg) { - checkArgument(!isNullOrEmpty(command), "command may not be null or empty"); - checkArgument(!isNullOrEmpty(arg), "arg may not be null or empty"); + Assert.isTrue(StringUtils.hasText(command), "command may not be null or empty"); + Assert.isTrue(StringUtils.hasText(arg), "arg may not be null or empty"); return sendApiSingleLineCommand(channel, "api " + command + ' ' + arg); } diff --git a/modules/call/src/main/java/com/bytedesk/call/esl/client/internal/Context.java b/modules/call/src/main/java/com/bytedesk/call/esl/client/internal/Context.java index 3b50686416..2fb0307d93 100644 --- a/modules/call/src/main/java/com/bytedesk/call/esl/client/internal/Context.java +++ b/modules/call/src/main/java/com/bytedesk/call/esl/client/internal/Context.java @@ -5,12 +5,11 @@ import com.bytedesk.call.esl.client.transport.CommandResponse; import com.bytedesk.call.esl.client.transport.SendMsg; import com.bytedesk.call.esl.client.transport.event.EslEvent; import com.bytedesk.call.esl.client.transport.message.EslMessage; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; import java.util.concurrent.CompletableFuture; -import static com.google.common.base.Preconditions.*; -import static com.google.common.base.Strings.isNullOrEmpty; -import static com.google.common.util.concurrent.Futures.getUnchecked; import static com.bytedesk.call.esl.client.internal.IModEslApi.EventFormat.*; public class Context implements IModEslApi { @@ -39,11 +38,11 @@ public class Context implements IModEslApi { */ public EslMessage sendCommand(String command) { - checkArgument(!isNullOrEmpty(command), "command cannot be null or empty"); + Assert.isTrue(StringUtils.hasText(command), "command cannot be null or empty"); try { - return getUnchecked(handler.sendApiSingleLineCommand(channel, command.toLowerCase().trim())); + return handler.sendApiSingleLineCommand(channel, command.toLowerCase().trim()).get(); } catch (Throwable t) { throw new RuntimeException(t); @@ -63,17 +62,17 @@ public class Context implements IModEslApi { @Override public EslMessage sendApiCommand(String command, String arg) { - checkArgument(!isNullOrEmpty(command), "command cannot be null or empty"); + Assert.isTrue(StringUtils.hasText(command), "command cannot be null or empty"); try { final StringBuilder sb = new StringBuilder(); sb.append("api ").append(command); - if (!isNullOrEmpty(arg)) { + if (StringUtils.hasText(arg)) { sb.append(' ').append(arg); } - return getUnchecked(handler.sendApiSingleLineCommand(channel, sb.toString())); + return handler.sendApiSingleLineCommand(channel, sb.toString()).get(); } catch (Throwable t) { throw new RuntimeException(t); @@ -94,11 +93,11 @@ public class Context implements IModEslApi { @Override public CompletableFuture sendBackgroundApiCommand(String command, String arg) { - checkArgument(!isNullOrEmpty(command), "command cannot be null or empty"); + Assert.isTrue(StringUtils.hasText(command), "command cannot be null or empty"); final StringBuilder sb = new StringBuilder(); sb.append("bgapi ").append(command); - if (!isNullOrEmpty(arg)) { + if (StringUtils.hasText(arg)) { sb.append(' ').append(arg); } @@ -126,17 +125,17 @@ public class Context implements IModEslApi { public CommandResponse setEventSubscriptions(EventFormat format, String events) { // temporary hack - checkState(format.equals(PLAIN), "Only 'plain' event format is supported at present"); + Assert.state(format.equals(PLAIN), "Only 'plain' event format is supported at present"); try { final StringBuilder sb = new StringBuilder(); sb.append("event ").append(format.toString()); - if (!isNullOrEmpty(events)) { + if (StringUtils.hasText(events)) { sb.append(' ').append(events); } - final EslMessage response = getUnchecked(handler.sendApiSingleLineCommand(channel, sb.toString())); + final EslMessage response = handler.sendApiSingleLineCommand(channel, sb.toString()).get(); return new CommandResponse(sb.toString(), response); } catch (Throwable t) { @@ -154,7 +153,7 @@ public class Context implements IModEslApi { public CommandResponse cancelEventSubscriptions() { try { - final EslMessage response = getUnchecked(handler.sendApiSingleLineCommand(channel, "noevents")); + final EslMessage response = handler.sendApiSingleLineCommand(channel, "noevents").get(); return new CommandResponse("noevents", response); } catch (Throwable t) { throw new RuntimeException(t); @@ -184,16 +183,16 @@ public class Context implements IModEslApi { @Override public CommandResponse addEventFilter(String eventHeader, String valueToFilter) { - checkArgument(!isNullOrEmpty(eventHeader), "eventHeader cannot be null or empty"); + Assert.isTrue(StringUtils.hasText(eventHeader), "eventHeader cannot be null or empty"); try { final StringBuilder sb = new StringBuilder(); sb.append("filter ").append(eventHeader); - if (!isNullOrEmpty(valueToFilter)) { + if (StringUtils.hasText(valueToFilter)) { sb.append(' ').append(valueToFilter); } - final EslMessage response = getUnchecked(handler.sendApiSingleLineCommand(channel, sb.toString())); + final EslMessage response = handler.sendApiSingleLineCommand(channel, sb.toString()).get(); return new CommandResponse(sb.toString(), response); } catch (Throwable t) { @@ -211,17 +210,17 @@ public class Context implements IModEslApi { @Override public CommandResponse deleteEventFilter(String eventHeader, String valueToFilter) { - checkArgument(!isNullOrEmpty(eventHeader), "eventHeader cannot be null or empty"); + Assert.isTrue(StringUtils.hasText(eventHeader), "eventHeader cannot be null or empty"); try { final StringBuilder sb = new StringBuilder(); sb.append("filter delete ").append(eventHeader); - if (!isNullOrEmpty(valueToFilter)) { + if (StringUtils.hasText(valueToFilter)) { sb.append(' ').append(valueToFilter); } - final EslMessage response = getUnchecked(handler.sendApiSingleLineCommand(channel, sb.toString())); + final EslMessage response = handler.sendApiSingleLineCommand(channel, sb.toString()).get(); return new CommandResponse(sb.toString(), response); } catch (Throwable t) { @@ -239,10 +238,10 @@ public class Context implements IModEslApi { @Override public CommandResponse sendMessage(SendMsg sendMsg) { - checkNotNull(sendMsg, "sendMsg cannot be null"); + Assert.notNull(sendMsg, "sendMsg cannot be null"); try { - final EslMessage response = getUnchecked(handler.sendApiMultiLineCommand(channel, sendMsg.getMsgLines())); + final EslMessage response = handler.sendApiMultiLineCommand(channel, sendMsg.getMsgLines()).get(); return new CommandResponse(sendMsg.toString(), response); } catch (Throwable t) { throw new RuntimeException(t); @@ -263,7 +262,7 @@ public class Context implements IModEslApi { final StringBuilder sb = new StringBuilder(); sb.append("log ").append(level.toString()); - final EslMessage response = getUnchecked(handler.sendApiSingleLineCommand(channel, sb.toString())); + final EslMessage response = handler.sendApiSingleLineCommand(channel, sb.toString()).get(); return new CommandResponse(sb.toString(), response); } catch (Throwable t) { throw new RuntimeException(t); @@ -280,7 +279,7 @@ public class Context implements IModEslApi { public CommandResponse cancelLogging() { try { - final EslMessage response = getUnchecked(handler.sendApiSingleLineCommand(channel, "nolog")); + final EslMessage response = handler.sendApiSingleLineCommand(channel, "nolog").get(); return new CommandResponse("nolog", response); } catch (Throwable t) { throw new RuntimeException(t); diff --git a/modules/call/src/main/java/com/bytedesk/call/esl/client/transport/event/EslEvent.java b/modules/call/src/main/java/com/bytedesk/call/esl/client/transport/event/EslEvent.java index 9d5fc8e4d9..340b8e4ab1 100644 --- a/modules/call/src/main/java/com/bytedesk/call/esl/client/transport/event/EslEvent.java +++ b/modules/call/src/main/java/com/bytedesk/call/esl/client/transport/event/EslEvent.java @@ -29,8 +29,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; - -import static com.google.common.base.MoreObjects.toStringHelper; +import java.util.StringJoiner; /** * FreeSWITCH Event Socket events are decoded into this data object. @@ -189,11 +188,11 @@ public class EslEvent { @Override public String toString() { - return toStringHelper(this) - .add("name", getEventName()) - .add("headers", messageHeaders.size()) - .add("eventHeaders", eventHeaders.size()) - .add("eventBody", eventBody.size() + " lines") + return new StringJoiner(", ", EslEvent.class.getSimpleName() + "{", "}") + .add("name=" + getEventName()) + .add("headers=" + messageHeaders.size()) + .add("eventHeaders=" + eventHeaders.size()) + .add("eventBody=" + eventBody.size() + " lines") .toString(); } } diff --git a/modules/call/src/main/java/com/bytedesk/call/esl/client/transport/message/EslMessage.java b/modules/call/src/main/java/com/bytedesk/call/esl/client/transport/message/EslMessage.java index b717ebfa14..ca0a998bf6 100644 --- a/modules/call/src/main/java/com/bytedesk/call/esl/client/transport/message/EslMessage.java +++ b/modules/call/src/main/java/com/bytedesk/call/esl/client/transport/message/EslMessage.java @@ -23,8 +23,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; - -import static com.google.common.base.MoreObjects.toStringHelper; +import java.util.StringJoiner; /** * Basic FreeSWITCH Event Socket messages from the server are decoded into this data object. @@ -148,10 +147,10 @@ public class EslMessage { @Override public String toString() { - return toStringHelper(this) - .add("contentType", getContentType()) - .add("headers", headers.size()) - .add("body", body.size() + " lines") + return new StringJoiner(", ", EslMessage.class.getSimpleName() + "{", "}") + .add("contentType=" + getContentType()) + .add("headers=" + headers.size()) + .add("body=" + body.size() + " lines") .toString(); } diff --git a/modules/core/src/main/java/com/bytedesk/core/email/EmailSendResult.java b/modules/core/src/main/java/com/bytedesk/core/email/EmailSendResult.java new file mode 100644 index 0000000000..6ac0cbca70 --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/email/EmailSendResult.java @@ -0,0 +1,66 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2025-12-05 10:00:00 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-12-05 10:00:00 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * 联系:270580156@qq.com + * Copyright (c) 2024 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.email; + +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * 邮件发送结果 + */ +@Data +@AllArgsConstructor +public class EmailSendResult { + + /** + * 是否成功 + */ + private boolean success; + + /** + * 错误类型 + */ + private SendCodeErrorType errorType; + + /** + * 错误信息 + */ + private String errorMessage; + + /** + * 成功结果 + */ + public static EmailSendResult success() { + return new EmailSendResult(true, null, null); + } + + /** + * 失败结果 + */ + public static EmailSendResult failure(SendCodeErrorType errorType, String errorMessage) { + return new EmailSendResult(false, errorType, errorMessage); + } + + /** + * 邮件发送错误类型枚举 + */ + public enum SendCodeErrorType { + /** 发送过于频繁 */ + TOO_FREQUENT, + /** 验证码已发送 */ + ALREADY_SENT, + /** 发送失败 */ + SEND_FAILED + } +} diff --git a/modules/core/src/main/java/com/bytedesk/core/email/EmailSendService.java b/modules/core/src/main/java/com/bytedesk/core/email/EmailSendService.java new file mode 100644 index 0000000000..5d3dfff69c --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/email/EmailSendService.java @@ -0,0 +1,204 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2024-03-31 15:30:19 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-12-05 10:00:00 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * 联系:270580156@qq.com + * Copyright (c) 2024 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.email; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Service; + +import com.aliyuncs.DefaultAcsClient; +import com.aliyuncs.IAcsClient; +import com.aliyuncs.dm.model.v20151123.SingleSendMailRequest; +import com.aliyuncs.exceptions.ClientException; +import com.aliyuncs.exceptions.ServerException; +import com.aliyuncs.profile.DefaultProfile; +import com.aliyuncs.profile.IClientProfile; +import com.bytedesk.core.config.properties.BytedeskProperties; +import com.bytedesk.core.utils.Utils; + +import jakarta.mail.internet.MimeMessage; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.Assert; + +/** + * 邮件发送服务 + * https://springdoc.cn/spring-boot-email/ + * https://springdoc.cn/spring/integration.html#mail + * https://mailtrap.io/blog/spring-send-email/ + * https://www.thymeleaf.org/doc/articles/springmail.html + * http://blog.didispace.com/springbootmailsender/ + */ +@Slf4j +@Service +public class EmailSendService { + + @Autowired + private BytedeskProperties bytedeskProperties; + + @Value("${aliyun.access.key.id:}") + private String accessKeyId; + + @Value("${aliyun.access.key.secret:}") + private String accessKeySecret; + + @Autowired + private JavaMailSender javaMailSender; + + @Value("${spring.mail.username:}") + private String from; + + /** + * 发送邮件 + * @param email 邮箱地址 + * @param content 邮件内容 + * @param request HTTP请求 + * @return 是否发送成功 + */ + public boolean sendEmail(String email, String content, HttpServletRequest request) { + return sendEmailWithResult(email, content, request).isSuccess(); + } + + /** + * 发送邮件并返回详细结果 + * @param email 邮箱地址 + * @param content 邮件内容 + * @param request HTTP请求 + * @return EmailSendResult 发送结果 + */ + public EmailSendResult sendEmailWithResult(String email, String content, HttpServletRequest request) { + Assert.hasText(email, "邮箱地址不能为空"); + Assert.hasText(content, "邮件内容不能为空"); + + // 测试邮箱不发送邮件 + if (Utils.isTestEmail(email)) { + return EmailSendResult.success(); // 测试邮箱认为发送成功 + } + + // 白名单邮箱使用固定验证码,无需真正发送验证码 + if (bytedeskProperties.isInWhitelist(email)) { + return EmailSendResult.success(); // 白名单邮箱认为发送成功 + } + + try { + boolean success; + if (bytedeskProperties.getEmailType().equals("aliyun")) { + success = sendAliyunValidateCode(email, content); + } else { + success = sendJavaMailValidateCode(email, content); + } + + if (success) { + return EmailSendResult.success(); + } else { + return EmailSendResult.failure(EmailSendResult.SendCodeErrorType.SEND_FAILED, "邮件发送失败"); + } + } catch (Exception e) { + log.error("发送邮件失败", e); + return EmailSendResult.failure(EmailSendResult.SendCodeErrorType.SEND_FAILED, "邮件发送异常: " + e.getMessage()); + } + } + + /** + * 通过阿里云邮件推送SDK发送 + * + * @param email Email + * @param code 验证码 + * @return 发送是否成功 + */ + public boolean sendAliyunValidateCode(String email, String code) { + Assert.hasText(email, "邮箱地址不能为空"); + Assert.hasText(code, "验证码不能为空"); + + log.info("sendValidateCode email={} ,code={}", email, code); + + // 如果是除杭州region外的其它region(如新加坡、澳洲Region),需要将下面的"cn-hangzhou"替换为"ap-southeast-1"、或"ap-southeast-2"。 + IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret); + IAcsClient client = new DefaultAcsClient(profile); + SingleSendMailRequest request = new SingleSendMailRequest(); + try { + request.setAccountName("notify@register.weiyuai.cn"); + request.setFromAlias("微语"); + request.setAddressType(1); + request.setTagName("notify"); + request.setReplyToAddress(true); + request.setToAddress(email); + request.setSubject("微语"); + request.setHtmlBody("您的验证码是" + code + ", 15分钟内有效。开源在线客服&企业IM系统, https://www.weiyuai.cn"); + client.getAcsResponse(request); + return true; // 发送成功 + } catch (ServerException e) { + log.error("阿里云邮件发送失败 - ServerException, ErrCode: {}", e.getErrCode(), e); + return false; + } catch (ClientException e) { + log.error("阿里云邮件发送失败 - ClientException, ErrCode: {}", e.getErrCode(), e); + return false; + } + } + + /** + * 发送验证码邮件 + * @param email 邮箱地址 + * @param code 验证码 + * @return 是否发送成功 + */ + public boolean sendJavaMailValidateCode(String email, String code) { + Assert.hasText(email, "邮箱地址不能为空"); + Assert.hasText(code, "验证码不能为空"); + + log.info("sendJavaMailValidateCode email={} ,code={}", email, code); + String content = "您的验证码是" + code + ", 15分钟内有效。开源在线客服&企业IM系统, https://www.weiyuai.cn"; + return sendJavaMail(email, "微语验证码", content); + } + + /** + * 通过JavaMail发送 + * https://springdoc.cn/spring-boot-email/ + * + * @param email 邮箱地址 + * @param subject 邮件主题 + * @param content 邮件内容 + * @return 发送是否成功 + */ + public boolean sendJavaMail(String email, String subject, String content) { + Assert.hasText(email, "邮箱地址不能为空"); + Assert.hasText(subject, "邮件主题不能为空"); + Assert.hasText(content, "邮件内容不能为空"); + + // 创建一个邮件消息 + MimeMessage message = javaMailSender.createMimeMessage(); + try { + // 创建 MimeMessageHelper + MimeMessageHelper helper = new MimeMessageHelper(message, false); + // 发件人邮箱和邮件中显示的发件人名字 + helper.setFrom(from, "weiyuai"); + // 收件人邮箱 + helper.setTo(email); + // 邮件标题 + helper.setSubject(subject); + // 邮件正文,第二个参数表示是否是HTML正文 + helper.setText(content, true); + + // 发送 + javaMailSender.send(message); + return true; // 发送成功 + } catch (Exception e) { + log.error("JavaMail发送邮件失败", e); + return false; + } + } + +} diff --git a/modules/core/src/main/java/com/bytedesk/core/push/PushService.java b/modules/core/src/main/java/com/bytedesk/core/push/PushService.java index 219a0f8dfe..99a5330214 100644 --- a/modules/core/src/main/java/com/bytedesk/core/push/PushService.java +++ b/modules/core/src/main/java/com/bytedesk/core/push/PushService.java @@ -35,18 +35,15 @@ public class PushService { private final PushRepository pushRepository; private final PushFilterService pushFilterService; - - // 业务服务组件 - private final PushSendService codeSendService; + private final PushSendService pushSendService; - // =============== REST接口方法 =============== /** * 发送验证码 */ public PushSendResult sendCode(AuthRequest authRequest, HttpServletRequest request) { - return codeSendService.sendCode(authRequest, request); + return pushSendService.sendCode(authRequest, request); } // =============== 业务逻辑方法 =============== diff --git a/modules/core/src/main/java/com/bytedesk/core/push/service/PushSendService.java b/modules/core/src/main/java/com/bytedesk/core/push/service/PushSendService.java index 54491fb204..2395a211b4 100644 --- a/modules/core/src/main/java/com/bytedesk/core/push/service/PushSendService.java +++ b/modules/core/src/main/java/com/bytedesk/core/push/service/PushSendService.java @@ -2,7 +2,7 @@ * @Author: jackning 270580156@qq.com * @Date: 2025-01-08 10:00:00 * @LastEditors: jackning 270580156@qq.com - * @LastEditTime: 2025-09-24 11:02:47 + * @LastEditTime: 2025-12-05 10:00:00 * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk * Please be aware of the BSL license restrictions before installing Bytedesk IM – * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. @@ -20,6 +20,8 @@ import org.springframework.util.Assert; import com.bytedesk.core.config.properties.BytedeskProperties; import com.bytedesk.core.constant.I18Consts; import com.bytedesk.core.constant.TypeConsts; +import com.bytedesk.core.email.EmailSendResult; +import com.bytedesk.core.email.EmailSendService; import com.bytedesk.core.ip.IpService; import com.bytedesk.core.ip.IpUtils; import com.bytedesk.core.push.PushEntity; @@ -31,6 +33,8 @@ import com.bytedesk.core.push.strategy.AuthValidationStrategy; import com.bytedesk.core.push.strategy.AuthValidationStrategyFactory; import com.bytedesk.core.rbac.auth.AuthRequest; import com.bytedesk.core.rbac.auth.AuthTypeEnum; +import com.bytedesk.core.sms.SmsSendResult; +import com.bytedesk.core.sms.SmsSendService; import com.bytedesk.core.utils.Utils; import com.bytedesk.core.push.PushFilterService; @@ -47,8 +51,8 @@ import lombok.extern.slf4j.Slf4j; public class PushSendService { private final AuthValidationStrategyFactory strategyFactory; - private final PushServiceEmail pushServiceEmail; - private final PushServiceSms pushServiceSms; + private final EmailSendService emailSendService; + private final SmsSendService smsSendService; private final BytedeskProperties bytedeskProperties; private final IpService ipService; private final PushFilterService pushFilterService; @@ -66,7 +70,6 @@ public class PushSendService { String ip = IpUtils.getClientIp(request); String type = authRequest.getType(); String receiver = authRequest.getReceiver(); - // String country = authRequest.getCountry(); String platform = authRequest.getPlatform(); // 验证IP频率限制 @@ -108,25 +111,48 @@ public class PushSendService { private PushSendResult sendCodeByType(AuthRequest authRequest, String receiver, String country, String code, HttpServletRequest request) { if (authRequest.isEmail()) { - return pushServiceEmail.sendEmailWithResult(receiver, code, request); + EmailSendResult emailResult = emailSendService.sendEmailWithResult(receiver, code, request); + return convertEmailResult(emailResult); } else if (authRequest.isMobile()) { - return pushServiceSms.sendSmsWithResult(receiver, country, code, request); + SmsSendResult smsResult = smsSendService.sendSmsWithResult(receiver, country, code, request); + return convertSmsResult(smsResult); } return PushSendResult.failure(PushSendResult.SendCodeErrorType.SEND_FAILED, I18Consts.I18N_CAPTCHA_UNSUPPORTED_TYPE); } + + /** + * 将EmailSendResult转换为PushSendResult + */ + private PushSendResult convertEmailResult(EmailSendResult emailResult) { + if (emailResult.isSuccess()) { + return PushSendResult.success(); + } + return PushSendResult.failure(PushSendResult.SendCodeErrorType.SEND_FAILED, emailResult.getErrorMessage()); + } + + /** + * 将SmsSendResult转换为PushSendResult + */ + private PushSendResult convertSmsResult(SmsSendResult smsResult) { + if (smsResult.isSuccess()) { + return PushSendResult.success(); + } + return PushSendResult.failure(PushSendResult.SendCodeErrorType.SEND_FAILED, smsResult.getErrorMessage()); + } private void saveCodeRecord(AuthRequest authRequest, String code, String ip, HttpServletRequest request) { String ipLocation = ipService.getIpLocation(ip); + String country = authRequest.getCountry(); + String receiver = authRequest.getReceiver(); // 重新发送并获取结果 - PushSendResult sendResult = sendCodeByType(authRequest, authRequest.getReceiver(), - authRequest.getCountry(), code, request); + PushSendResult sendResult = sendCodeByType(authRequest, receiver, country, code, request); PushRequest pushRequest = new PushRequest(); // 复制必要的字段 pushRequest.setType(authRequest.getType()); - pushRequest.setCountry(authRequest.getCountry()); - pushRequest.setReceiver(authRequest.getReceiver()); + pushRequest.setCountry(country); + pushRequest.setReceiver(receiver); pushRequest.setPlatform(authRequest.getPlatform()); pushRequest.setSender(TypeConsts.TYPE_SYSTEM); pushRequest.setContent(code); @@ -196,9 +222,11 @@ public class PushSendService { private PushSendResult resendByType(String type, String receiver, String country, String content) { // 判断是邮箱还是手机号类型 if (isEmailType(type)) { - return pushServiceEmail.sendEmailWithResult(receiver, content, null); + EmailSendResult emailResult = emailSendService.sendEmailWithResult(receiver, content, null); + return convertEmailResult(emailResult); } else if (isMobileType(type)) { - return pushServiceSms.sendSmsWithResult(receiver, country, content, null); + SmsSendResult smsResult = smsSendService.sendSmsWithResult(receiver, country, content, null); + return convertSmsResult(smsResult); } return PushSendResult.failure(PushSendResult.SendCodeErrorType.SEND_FAILED, "不支持的推送类型"); } diff --git a/modules/core/src/main/java/com/bytedesk/core/push/service/PushServiceEmail.java b/modules/core/src/main/java/com/bytedesk/core/push/service/PushServiceEmail.java index b7251cd463..6076b50f7a 100644 --- a/modules/core/src/main/java/com/bytedesk/core/push/service/PushServiceEmail.java +++ b/modules/core/src/main/java/com/bytedesk/core/push/service/PushServiceEmail.java @@ -2,7 +2,7 @@ * @Author: jackning 270580156@qq.com * @Date: 2024-03-31 15:30:19 * @LastEditors: jackning 270580156@qq.com - * @LastEditTime: 2025-09-19 09:49:16 + * @LastEditTime: 2025-12-05 10:00:00 * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk * Please be aware of the BSL license restrictions before installing Bytedesk IM – * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. @@ -13,206 +13,79 @@ */ package com.bytedesk.core.push.service; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.mail.javamail.JavaMailSender; -import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.stereotype.Service; -import com.aliyuncs.DefaultAcsClient; -import com.aliyuncs.IAcsClient; -import com.aliyuncs.dm.model.v20151123.SingleSendMailRequest; -import com.aliyuncs.exceptions.ClientException; -import com.aliyuncs.exceptions.ServerException; -import com.aliyuncs.profile.DefaultProfile; -import com.aliyuncs.profile.IClientProfile; -import com.bytedesk.core.config.properties.BytedeskProperties; -import com.bytedesk.core.utils.Utils; +import com.bytedesk.core.email.EmailSendResult; +import com.bytedesk.core.email.EmailSendService; -import jakarta.mail.internet.MimeMessage; import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.util.Assert; /** - * https://springdoc.cn/spring-boot-email/ - * https://springdoc.cn/spring/integration.html#mail - * https://mailtrap.io/blog/spring-send-email/ - * https://www.thymeleaf.org/doc/articles/springmail.html - * http://blog.didispace.com/springbootmailsender/ + * Push邮件服务 - 委托给EmailSendService + * @deprecated 请直接使用 {@link EmailSendService} */ @Slf4j @Service +@RequiredArgsConstructor +@Deprecated public class PushServiceEmail { - @Autowired - private BytedeskProperties bytedeskProperties; - - @Value("${aliyun.access.key.id:}") - private String accessKeyId; - - @Value("${aliyun.access.key.secret:}") - private String accessKeySecret; - - @Autowired - private JavaMailSender javaMailSender; - - @Value("${spring.mail.username:}") - private String from; + private final EmailSendService emailSendService; + /** + * 发送邮件 + * @deprecated 请直接使用 {@link EmailSendService#sendEmail} + */ + @Deprecated public boolean sendEmail(String email, String content, HttpServletRequest request) { - // 为了保持向后兼容,调用新的方法并返回结果 - return sendEmailWithResult(email, content, request).isSuccess(); + log.warn("PushServiceEmail.sendEmail is deprecated, please use EmailSendService.sendEmail instead"); + return emailSendService.sendEmail(email, content, request); } /** * 发送邮件并返回详细结果 - * @param email 邮箱地址 - * @param content 邮件内容 - * @param request HTTP请求 - * @return SendCodeResult 发送结果 + * @deprecated 请直接使用 {@link EmailSendService#sendEmailWithResult} */ + @Deprecated public PushSendResult sendEmailWithResult(String email, String content, HttpServletRequest request) { - Assert.hasText(email, "邮箱地址不能为空"); - Assert.hasText(content, "邮件内容不能为空"); - - // log.info("send email to {}, content {}", email, content); - - // 测试邮箱不发送邮件 - if (Utils.isTestEmail(email)) { - return PushSendResult.success(); // 测试邮箱认为发送成功 - } - - // 白名单邮箱使用固定验证码,无需真正发送验证码 - if (bytedeskProperties.isInWhitelist(email)) { - return PushSendResult.success(); // 白名单邮箱认为发送成功 - } - - // 调试模式不发送邮件 - // if (bytedeskProperties.getDebug()) { - // return SendCodeResult.success(); // 调试模式认为发送成功 - // } - - try { - boolean success; - if (bytedeskProperties.getEmailType().equals("aliyun")) { - success = sendAliyunValidateCode(email, content); - } else { - success = sendJavaMailValidateCode(email, content); - } - - if (success) { - return PushSendResult.success(); - } else { - return PushSendResult.failure(PushSendResult.SendCodeErrorType.SEND_FAILED, "邮件发送失败"); - } - } catch (Exception e) { - log.error("发送邮件失败", e); - return PushSendResult.failure(PushSendResult.SendCodeErrorType.SEND_FAILED, "邮件发送异常: " + e.getMessage()); + log.warn("PushServiceEmail.sendEmailWithResult is deprecated, please use EmailSendService.sendEmailWithResult instead"); + EmailSendResult result = emailSendService.sendEmailWithResult(email, content, request); + if (result.isSuccess()) { + return PushSendResult.success(); } + return PushSendResult.failure(PushSendResult.SendCodeErrorType.SEND_FAILED, result.getErrorMessage()); } /** * 通过阿里云邮件推送SDK发送 - * - * @param email Email - * @param code 验证码 - * @return 发送是否成功 + * @deprecated 请直接使用 {@link EmailSendService#sendAliyunValidateCode} */ + @Deprecated public boolean sendAliyunValidateCode(String email, String code) { - Assert.hasText(email, "邮箱地址不能为空"); - Assert.hasText(code, "验证码不能为空"); - - log.info("sendValidateCode email={} ,code={}", email, code); - - // TODO: 检测同一个ip是否短时间内有发送过验证码,如果短时间内发送过,则不发送 - - // 如果是除杭州region外的其它region(如新加坡、澳洲Region),需要将下面的"cn-hangzhou"替换为"ap-southeast-1"、或"ap-southeast-2"。 - IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret); - // 如果是除杭州region外的其它region(如新加坡region), 需要做如下处理 - // try { - // DefaultProfile.addEndpoint("dm.ap-southeast-1.aliyuncs.com", - // "ap-southeast-1", "Dm", "dm.ap-southeast-1.aliyuncs.com"); - // } catch (ClientException e) { - // e.printStackTrace(); - // } - IAcsClient client = new DefaultAcsClient(profile); - SingleSendMailRequest request = new SingleSendMailRequest(); - try { - // request.setVersion("2017-06-22");// - // 如果是除杭州region外的其它region(如新加坡region),必须指定为2017-06-22 - request.setAccountName("notify@register.weiyuai.cn"); - request.setFromAlias("微语"); - request.setAddressType(1); - request.setTagName("notify"); - request.setReplyToAddress(true); - request.setToAddress(email); - // 可以给多个收件人发送邮件,收件人之间用逗号分开,批量发信建议使用BatchSendMailRequest方式 - // request.setToAddress("邮箱1,邮箱2"); - request.setSubject("微语"); - request.setHtmlBody("您的验证码是" + code + ", 15分钟内有效。开源在线客服&企业IM系统, https://www.weiyuai.cn"); - // 开启需要备案,0关闭,1开启 - // request.setClickTrace("0"); - // 如果调用成功,正常返回httpResponse;如果调用失败则抛出异常,需要在异常中捕获错误异常码;错误异常码请参考对应的API文档; - // SingleSendMailResponse httpResponse = - client.getAcsResponse(request); - return true; // 发送成功 - } catch (ServerException e) { - // 捕获错误异常码 - log.error("阿里云邮件发送失败 - ServerException, ErrCode: {}", e.getErrCode(), e); - return false; - } catch (ClientException e) { - // 捕获错误异常码 - log.error("阿里云邮件发送失败 - ClientException, ErrCode: {}", e.getErrCode(), e); - return false; - } + log.warn("PushServiceEmail.sendAliyunValidateCode is deprecated, please use EmailSendService.sendAliyunValidateCode instead"); + return emailSendService.sendAliyunValidateCode(email, code); } - + /** + * 发送验证码邮件 + * @deprecated 请直接使用 {@link EmailSendService#sendJavaMailValidateCode} + */ + @Deprecated public boolean sendJavaMailValidateCode(String email, String code) { - Assert.hasText(email, "邮箱地址不能为空"); - Assert.hasText(code, "验证码不能为空"); - - log.info("sendJavaMailValidateCode email={} ,code={}", email, code); - // - String content = "您的验证码是" + code + ", 15分钟内有效。开源在线客服&企业IM系统, https://www.weiyuai.cn"; - return sendJavaMail(email, "微语验证码", content); + log.warn("PushServiceEmail.sendJavaMailValidateCode is deprecated, please use EmailSendService.sendJavaMailValidateCode instead"); + return emailSendService.sendJavaMailValidateCode(email, code); } /** * 通过JavaMail发送 - * https://springdoc.cn/spring-boot-email/ - * - * @param email - * @param content - * @return 发送是否成功 + * @deprecated 请直接使用 {@link EmailSendService#sendJavaMail} */ + @Deprecated public boolean sendJavaMail(String email, String subject, String content) { - Assert.hasText(email, "邮箱地址不能为空"); - Assert.hasText(subject, "邮件主题不能为空"); - Assert.hasText(content, "邮件内容不能为空"); - - // 创建一个邮件消息 - MimeMessage message = javaMailSender.createMimeMessage(); - try { - // 创建 MimeMessageHelper - MimeMessageHelper helper = new MimeMessageHelper(message, false); - // 发件人邮箱和邮件中显示的发件人名字 - helper.setFrom(from, "weiyuai"); - // 收件人邮箱 - helper.setTo(email); - // 邮件标题 - helper.setSubject(subject); - // 邮件正文,第二个参数表示是否是HTML正文 - helper.setText(content, true); - - // 发送 - javaMailSender.send(message); - return true; // 发送成功 - } catch (Exception e) { - log.error("JavaMail发送邮件失败", e); - return false; - } + log.warn("PushServiceEmail.sendJavaMail is deprecated, please use EmailSendService.sendJavaMail instead"); + return emailSendService.sendJavaMail(email, subject, content); } } diff --git a/modules/core/src/main/java/com/bytedesk/core/push/service/PushServiceSms.java b/modules/core/src/main/java/com/bytedesk/core/push/service/PushServiceSms.java index cb81ff1b9c..8ada344bad 100644 --- a/modules/core/src/main/java/com/bytedesk/core/push/service/PushServiceSms.java +++ b/modules/core/src/main/java/com/bytedesk/core/push/service/PushServiceSms.java @@ -2,7 +2,7 @@ * @Author: jackning 270580156@qq.com * @Date: 2024-03-31 15:29:55 * @LastEditors: jackning 270580156@qq.com - * @LastEditTime: 2025-09-19 15:15:42 + * @LastEditTime: 2025-12-05 10:00:00 * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk * Please be aware of the BSL license restrictions before installing Bytedesk IM – * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. @@ -13,252 +13,63 @@ */ package com.bytedesk.core.push.service; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; -import com.aliyuncs.CommonRequest; -import com.aliyuncs.CommonResponse; -import com.aliyuncs.DefaultAcsClient; -import com.aliyuncs.IAcsClient; -import com.aliyuncs.exceptions.ClientException; -import com.aliyuncs.exceptions.ServerException; -import com.aliyuncs.http.MethodType; -import com.aliyuncs.profile.DefaultProfile; -import com.bytedesk.core.config.properties.BytedeskProperties; -import com.bytedesk.core.utils.Utils; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.bytedesk.core.sms.SmsSendResult; +import com.bytedesk.core.sms.SmsSendService; import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.util.Assert; +/** + * Push短信服务 - 委托给SmsSendService + * @deprecated 请直接使用 {@link SmsSendService} + */ @Slf4j @Service +@RequiredArgsConstructor +@Deprecated public class PushServiceSms { - @Value("${aliyun.region.id:cn-hangzhou}") - private String regionId; + private final SmsSendService smsSendService; - @Value("${aliyun.access.key.id:}") - private String accessKeyId; - - @Value("${aliyun.access.key.secret:}") - private String accessKeySecret; - - @Value("${aliyun.sms.signname:}") - private String signName; - - @Value("${aliyun.sms.templatecode:}") - private String templateCode; - /** - * 初始化时处理配置项编码问题 + * 发送短信 + * @deprecated 请直接使用 {@link SmsSendService#sendSms} */ - @Autowired - public void init() { - try { - // 检查签名是否为乱码,如果是则进行转换 - if (signName != null && !signName.isEmpty()) { - boolean needsConversion = false; - for (char c : signName.toCharArray()) { - if (c > 0x7F) { // 非ASCII字符 - needsConversion = true; - break; - } - } - - if (needsConversion) { - signName = new String(signName.getBytes("ISO-8859-1"), "UTF-8"); - log.info("短信签名编码转换完成: {}", signName); - } - } - } catch (Exception e) { - log.error("短信签名编码转换失败", e); - } - } - - @Autowired - private BytedeskProperties bytedeskProperties; - - private final ObjectMapper objectMapper = new ObjectMapper(); - + @Deprecated public boolean sendSms(String mobile, String country, String content, HttpServletRequest request) { - PushSendResult result = sendSmsWithResult(mobile, country, content, request); - return result.isSuccess(); + log.warn("PushServiceSms.sendSms is deprecated, please use SmsSendService.sendSms instead"); + return smsSendService.sendSms(mobile, country, content, request); } /** * 发送短信并返回详细结果 - * @param mobile 手机号 - * @param country 国家代码 - * @param content 短信内容 - * @param request HTTP请求 - * @return SendCodeResult 发送结果 + * @deprecated 请直接使用 {@link SmsSendService#sendSmsWithResult} */ + @Deprecated public PushSendResult sendSmsWithResult(String mobile, String country, String content, HttpServletRequest request) { - Assert.hasText(mobile, "手机号不能为空"); - Assert.hasText(content, "短信内容不能为空"); - - log.info("send sms to {}, country: {}, content: {}", mobile, country, content); - - // 测试手机号不发送验证码 - if (Utils.isTestMobile(mobile)) { - return PushSendResult.success(); // 测试手机号认为发送成功 - } - - // 白名单手机号使用固定验证码,无需真正发送验证码 - if (bytedeskProperties.isInWhitelist(mobile)) { - return PushSendResult.success(); // 白名单手机号认为发送成功 - } - - // 测试环境不发送验证码 - // if (bytedeskProperties.getDebug()) { - // return SendCodeResult.success(); // 测试环境认为发送成功 - // } - - try { - return sendValidateCode(mobile, country, content); - } catch (Exception e) { - log.error("发送短信失败", e); - return PushSendResult.failure(PushSendResult.SendCodeErrorType.SEND_FAILED, "发送短信异常: " + e.getMessage()); + log.warn("PushServiceSms.sendSmsWithResult is deprecated, please use SmsSendService.sendSmsWithResult instead"); + SmsSendResult result = smsSendService.sendSmsWithResult(mobile, country, content, request); + if (result.isSuccess()) { + return PushSendResult.success(); } + return PushSendResult.failure(PushSendResult.SendCodeErrorType.SEND_FAILED, result.getErrorMessage()); } + /** + * 发送验证码 + * @deprecated 请直接使用 {@link SmsSendService#sendValidateCode} + */ + @Deprecated public PushSendResult sendValidateCode(String mobile, String country, String code) { - Assert.hasText(mobile, "手机号不能为空"); - Assert.hasText(code, "验证码不能为空"); - - log.info("sendValidateCode sms to {}, country: {}, code: {}", mobile, country, code); - - // 处理国家代码:只保留数字,中国86可以不添加前缀 - String phoneNumber = formatPhoneNumber(mobile, country); - log.debug("格式化后的手机号: {}", phoneNumber); - - DefaultProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret); - IAcsClient client = new DefaultAcsClient(profile); - - CommonRequest request = new CommonRequest(); - request.setSysMethod(MethodType.POST); - request.setSysDomain("dysmsapi.aliyuncs.com"); - request.setSysVersion("2017-05-25"); - request.setSysAction("SendSms"); - request.putQueryParameter("RegionId", regionId); - request.putQueryParameter("PhoneNumbers", phoneNumber); - // 已在init方法中处理了编码问题,此处直接使用 - log.debug("配置文件签名:{}", signName); - request.putQueryParameter("SignName", signName); - request.putQueryParameter("TemplateCode", templateCode); - request.putQueryParameter("TemplateParam", "{\"code\":\"" + code + "\"}"); - try { - CommonResponse response = client.getCommonResponse(request); - // 发送失败提示:{"Message":"手机号码格式错误","RequestId":"42DC3C7D-DABE-5E13-AB10-873060508C47","Code":"isv.MOBILE_NUMBER_ILLEGAL"} - // 发送成功提示:{"Message":"OK","RequestId":"1EA51590-4DBF-51EC-9FEC-812E7193C74D","Code":"OK","BizId":"458315458265098373^0"} - log.info("sendValidateCode sms response: {}", response.getData()); - - // 解析响应结果 - return parseAliyunSmsResponse(response.getData()); - } catch (ServerException e) { - log.error("阿里云短信发送失败 - ServerException", e); - return PushSendResult.failure(PushSendResult.SendCodeErrorType.SEND_FAILED, "阿里云服务器错误: " + e.getErrMsg()); - } catch (ClientException e) { - log.error("阿里云短信发送失败 - ClientException", e); - return PushSendResult.failure(PushSendResult.SendCodeErrorType.SEND_FAILED, "阿里云客户端错误: " + e.getErrMsg()); - } - } - - /** - * 格式化手机号码,处理国家代码 - * @param mobile 手机号 - * @param country 国家代码 - * @return 格式化后的手机号 - */ - private String formatPhoneNumber(String mobile, String country) { - // 处理国家代码:只保留数字 - String cleanCountry = ""; - if (country != null && !country.isEmpty()) { - cleanCountry = country.replaceAll("[^0-9]", ""); - } - - // 中国86区号可以不添加前缀 - if ("86".equals(cleanCountry)) { - return mobile; - } - - // 其他国家需要添加国家代码前缀 - if (!cleanCountry.isEmpty()) { - return cleanCountry + mobile; - } - - // 如果没有国家代码,默认返回手机号(中国手机号) - return mobile; - } - - /** - * 解析阿里云短信服务响应 - * @param responseData 响应JSON数据 - * @return SendCodeResult - */ - private PushSendResult parseAliyunSmsResponse(String responseData) { - try { - JsonNode jsonNode = objectMapper.readTree(responseData); - String code = jsonNode.get("Code").asText(); - String message = jsonNode.get("Message").asText(); - - // 判断是否发送成功 - if ("OK".equalsIgnoreCase(code)) { - return PushSendResult.success(); - } else { - // 根据错误代码返回中文错误信息 - String errorMessage = getChineseErrorMessage(code, message); - return PushSendResult.failure(PushSendResult.SendCodeErrorType.SEND_FAILED, errorMessage); - } - } catch (Exception e) { - log.error("解析阿里云短信响应失败", e); - return PushSendResult.failure(PushSendResult.SendCodeErrorType.SEND_FAILED, "解析短信服务响应失败"); - } - } /** - * 根据阿里云错误代码返回中文错误信息 - * @param code 错误代码 - * @param originalMessage 原始错误信息 - * @return 中文错误信息 - */ - private String getChineseErrorMessage(String code, String originalMessage) { - switch (code) { - case "isv.MOBILE_NUMBER_ILLEGAL": - return "手机号码格式错误"; - case "isv.MOBILE_COUNT_OVER_LIMIT": - return "手机号码数量超过限制"; - case "isv.TEMPLATE_MISSING_PARAMETERS": - return "短信模板参数缺失"; - case "isv.BUSINESS_LIMIT_CONTROL": - return "业务限流"; - case "isv.INVALID_PARAMETERS": - return "参数错误"; - case "isv.SYSTEM_ERROR": - return "系统错误"; - case "isv.OUT_OF_SERVICE": - return "服务不可用"; - case "SignatureNonce.Duplicate": - return "重复请求"; - case "InvalidTimeStamp.Expired": - return "时间戳过期"; - case "SignatureDoesNotMatch": - return "签名验证失败"; - case "InvalidAccessKeyId.NotFound": - return "AccessKey不存在"; - case "Forbidden.RAM": - return "RAM权限不足"; - case "isv.DAY_LIMIT_CONTROL": - return "日发送量超限"; - case "isv.SMS_CONTENT_ILLEGAL": - return "短信内容包含违禁词"; - case "isv.SMS_SIGN_ILLEGAL": - return "短信签名不合规"; - default: - return "短信发送失败: " + originalMessage; + log.warn("PushServiceSms.sendValidateCode is deprecated, please use SmsSendService.sendValidateCode instead"); + SmsSendResult result = smsSendService.sendValidateCode(mobile, country, code); + if (result.isSuccess()) { + return PushSendResult.success(); } + return PushSendResult.failure(PushSendResult.SendCodeErrorType.SEND_FAILED, result.getErrorMessage()); } } diff --git a/modules/core/src/main/java/com/bytedesk/core/sms/SmsEntity.java b/modules/core/src/main/java/com/bytedesk/core/sms/SmsEntity.java new file mode 100644 index 0000000000..01bb5caa93 --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/sms/SmsEntity.java @@ -0,0 +1,69 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2024-05-11 18:14:28 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-06-04 15:35:31 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * 联系:270580156@qq.com + * Copyright (c) 2024 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.sms; + +import com.bytedesk.core.base.BaseEntity; +import com.bytedesk.core.constant.I18Consts; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +// import jakarta.persistence.EntityListeners; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import lombok.experimental.SuperBuilder; + +/** + * Sms entity for content categorization and organization + * Provides smsging functionality for various system entities + * + * Database Table: bytedesk_core_sms + * Purpose: Stores sms definitions, colors, and organization settings + */ +@Entity +@Data +@SuperBuilder +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = true) +@AllArgsConstructor +@NoArgsConstructor +// @EntityListeners({SmsEntityListener.class}) +@Table(name = "bytedesk_core_sms") +public class SmsEntity extends BaseEntity { + + private static final long serialVersionUID = 1L; + + /** + * Name of the sms + */ + private String name; + + /** + * Description of the sms + */ + @Builder.Default + private String description = I18Consts.I18N_DESCRIPTION; + + /** + * Type of sms (CUSTOMER, TICKET, ARTICLE, etc.) + */ + @Builder.Default + @Column(name = "sms_type") + private String type = SmsTypeEnum.CUSTOMER.name(); + +} diff --git a/modules/core/src/main/java/com/bytedesk/core/sms/SmsEntityListener.java b/modules/core/src/main/java/com/bytedesk/core/sms/SmsEntityListener.java new file mode 100644 index 0000000000..9d05cf2fd9 --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/sms/SmsEntityListener.java @@ -0,0 +1,51 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2025-02-25 09:52:34 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-03-20 17:00:07 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * + * Copyright (c) 2025 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.sms; + +import org.springframework.stereotype.Component; +import org.springframework.util.SerializationUtils; + +import com.bytedesk.core.config.BytedeskEventPublisher; +import com.bytedesk.core.sms.event.SmsCreateEvent; +import com.bytedesk.core.sms.event.SmsUpdateEvent; +import com.bytedesk.core.utils.ApplicationContextHolder; + +import jakarta.persistence.PostPersist; +import jakarta.persistence.PostUpdate; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +public class SmsEntityListener { + + @PostPersist + public void onPostPersist(SmsEntity sms) { + log.info("onPostPersist: {}", sms); + SmsEntity cloneSms = SerializationUtils.clone(sms); + // + BytedeskEventPublisher bytedeskEventPublisher = ApplicationContextHolder.getBean(BytedeskEventPublisher.class); + bytedeskEventPublisher.publishEvent(new SmsCreateEvent(cloneSms)); + } + + @PostUpdate + public void onPostUpdate(SmsEntity sms) { + log.info("onPostUpdate: {}", sms); + SmsEntity cloneSms = SerializationUtils.clone(sms); + // + BytedeskEventPublisher bytedeskEventPublisher = ApplicationContextHolder.getBean(BytedeskEventPublisher.class); + bytedeskEventPublisher.publishEvent(new SmsUpdateEvent(cloneSms)); + } + +} diff --git a/modules/core/src/main/java/com/bytedesk/core/sms/SmsEventListener.java b/modules/core/src/main/java/com/bytedesk/core/sms/SmsEventListener.java new file mode 100644 index 0000000000..db5984c8ab --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/sms/SmsEventListener.java @@ -0,0 +1,39 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2025-02-25 09:44:18 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-06-04 15:50:06 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * + * Copyright (c) 2025 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.sms; + +import org.springframework.stereotype.Component; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +@AllArgsConstructor +public class SmsEventListener { + + // private final SmsRestService smsRestService; + + // @Order(3) + // @EventListener + // public void onOrganizationCreateEvent(OrganizationCreateEvent event) { + // OrganizationEntity organization = (OrganizationEntity) event.getSource(); + // String orgUid = organization.getUid(); + // log.info("thread - organization created: {}", organization.getName()); + // smsRestService.initSmss(orgUid); + // } + + +} + diff --git a/modules/core/src/main/java/com/bytedesk/core/sms/SmsExcel.java b/modules/core/src/main/java/com/bytedesk/core/sms/SmsExcel.java new file mode 100644 index 0000000000..9e35c31141 --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/sms/SmsExcel.java @@ -0,0 +1,47 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2024-08-01 06:18:10 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-07-04 18:00:51 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * 联系:270580156@qq.com + * Copyright (c) 2024 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.sms; + +import java.time.ZonedDateTime; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.annotation.format.DateTimeFormat; +import com.alibaba.excel.annotation.write.style.ColumnWidth; + +import lombok.Data; + +/** + * https://github.com/alibaba/easyexcel + */ +@Data +public class SmsExcel { + + @ExcelProperty(index = 0, value = "标签名称") + @ColumnWidth(20) + private String name; + + @ExcelProperty(index = 1, value = "类型") + @ColumnWidth(20) + private String type; + + @ExcelProperty(index = 2, value = "颜色") + @ColumnWidth(20) + private String color; + + @DateTimeFormat("yyyy-MM-dd HH:mm:ss") + @ExcelProperty(value = "创建时间", converter = com.bytedesk.core.converter.ZonedDateTimeConverter.class) + @ColumnWidth(25) + private ZonedDateTime createdAt; + +} diff --git a/modules/core/src/main/java/com/bytedesk/core/sms/SmsInitData.java b/modules/core/src/main/java/com/bytedesk/core/sms/SmsInitData.java new file mode 100644 index 0000000000..ef63dc89f7 --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/sms/SmsInitData.java @@ -0,0 +1,122 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2025-03-11 08:54:35 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-06-04 17:12:37 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * + * Copyright (c) 2025 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.sms; + +// import com.bytedesk.core.constant.I18Consts; + +public class SmsInitData { + + /** + * Technical Support Smss + * 技术支持标签 + */ + public static final String[] TECHNICAL_SUPPORT = { + // I18Consts.I18N_PREFIX + "thread.sms.technical_support", // parent + "技术支持", // parent + }; + + /** + * Service Request Smss + * 服务请求标签 + */ + public static final String[] SERVICE_REQUEST = { + // I18Consts.I18N_PREFIX + "thread.sms.service_request", // parent + "服务请求", // parent + }; + + /** + * Consultation Smss + * 咨询标签 + */ + public static final String[] CONSULTATION = { + // I18Consts.I18N_PREFIX + "thread.sms.consultation", // parent + "咨询", // parent + }; + + /** + * Complaint & Suggestion Smss + * 投诉与建议标签 + */ + public static final String[] COMPLAINT_SUGGESTION = { + // I18Consts.I18N_PREFIX + "thread.sms.complaint_suggestion", // parent + "投诉建议", // parent + }; + + /** + * Operation & Maintenance Smss + * 运维标签 + */ + public static final String[] OPERATION_MAINTENANCE = { + // I18Consts.I18N_PREFIX + "thread.sms.operation_maintenance", // parent + "运维", // parent + // 其他 + // I18Consts.I18N_PREFIX + "thread.sms.other", + "其他", + }; + + /** + * Helper method to determine if a sms is a parent sms + * + * @param sms The sms key to check + * @return true if it's a parent sms + */ + public static boolean isParentSms(String sms) { + return !sms.contains("."); + } + + /** + * Helper method to get parent sms key for a child sms + * + * @param childSms The child sms key + * @return The parent sms key + */ + public static String getParentSms(String childSms) { + if (isParentSms(childSms)) { + return null; + } + // 由于已将常量转为中文,此方法可能需要重新实现 + // 这里仅保留基本结构,具体实现需要根据新的标签体系来调整 + return null; + } + + /** + * Get all smss as a single array + * + * @return Array containing all smss + */ + public static String[] getAllSmss() { + int totalLength = TECHNICAL_SUPPORT.length + SERVICE_REQUEST.length + + CONSULTATION.length + COMPLAINT_SUGGESTION.length + + OPERATION_MAINTENANCE.length; + + String[] allSmss = new String[totalLength]; + int index = 0; + + System.arraycopy(TECHNICAL_SUPPORT, 0, allSmss, index, TECHNICAL_SUPPORT.length); + index += TECHNICAL_SUPPORT.length; + + System.arraycopy(SERVICE_REQUEST, 0, allSmss, index, SERVICE_REQUEST.length); + index += SERVICE_REQUEST.length; + + System.arraycopy(CONSULTATION, 0, allSmss, index, CONSULTATION.length); + index += CONSULTATION.length; + + System.arraycopy(COMPLAINT_SUGGESTION, 0, allSmss, index, COMPLAINT_SUGGESTION.length); + index += COMPLAINT_SUGGESTION.length; + + System.arraycopy(OPERATION_MAINTENANCE, 0, allSmss, index, OPERATION_MAINTENANCE.length); + + return allSmss; + } +} \ No newline at end of file diff --git a/modules/core/src/main/java/com/bytedesk/core/sms/SmsInitializer.java b/modules/core/src/main/java/com/bytedesk/core/sms/SmsInitializer.java new file mode 100644 index 0000000000..93b4524b30 --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/sms/SmsInitializer.java @@ -0,0 +1,44 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2024-11-06 21:43:58 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-08-26 10:52:24 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * 联系:270580156@qq.com + * Copyright (c) 2024 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.sms; + +import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.stereotype.Component; + +import lombok.AllArgsConstructor; + +@Component +@AllArgsConstructor +public class SmsInitializer implements SmartInitializingSingleton { + + // private final SmsRestService smsRestService; + + @Override + public void afterSingletonsInstantiated() { + initPermissions(); + // create default + // String orgUid = BytedeskConsts.DEFAULT_ORGANIZATION_UID; + // smsRestService.initSmss(orgUid); + } + + private void initPermissions() { + // for (PermissionEnum permission : PermissionEnum.values()) { + // String permissionValue = SmsPermissions.ARTICLE_PREFIX + permission.name(); + // authorityService.createForPlatform(permissionValue); + // } + } + + + +} diff --git a/modules/core/src/main/java/com/bytedesk/core/sms/SmsPermissions.java b/modules/core/src/main/java/com/bytedesk/core/sms/SmsPermissions.java new file mode 100644 index 0000000000..25ef14ccb0 --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/sms/SmsPermissions.java @@ -0,0 +1,73 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2024-11-05 16:58:18 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-05-06 11:55:32 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * 联系:270580156@qq.com + * Copyright (c) 2024 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.sms; + +import com.bytedesk.core.base.BasePermissions; + +/** + * 短信权限控制 - 五级权限体系 + */ +public class SmsPermissions extends BasePermissions { + + public static final String SMS_PREFIX = "SMS_"; + + // 平台级别权限 + public static final String SMS_PLATFORM_CREATE = "SMS_PLATFORM_CREATE"; + public static final String SMS_PLATFORM_READ = "SMS_PLATFORM_READ"; + public static final String SMS_PLATFORM_UPDATE = "SMS_PLATFORM_UPDATE"; + public static final String SMS_PLATFORM_DELETE = "SMS_PLATFORM_DELETE"; + public static final String SMS_PLATFORM_EXPORT = "SMS_PLATFORM_EXPORT"; + + // 组织级别权限 + public static final String SMS_ORGANIZATION_CREATE = "SMS_ORGANIZATION_CREATE"; + public static final String SMS_ORGANIZATION_READ = "SMS_ORGANIZATION_READ"; + public static final String SMS_ORGANIZATION_UPDATE = "SMS_ORGANIZATION_UPDATE"; + public static final String SMS_ORGANIZATION_DELETE = "SMS_ORGANIZATION_DELETE"; + public static final String SMS_ORGANIZATION_EXPORT = "SMS_ORGANIZATION_EXPORT"; + + // 部门级别权限 + public static final String SMS_DEPARTMENT_CREATE = "SMS_DEPARTMENT_CREATE"; + public static final String SMS_DEPARTMENT_READ = "SMS_DEPARTMENT_READ"; + public static final String SMS_DEPARTMENT_UPDATE = "SMS_DEPARTMENT_UPDATE"; + public static final String SMS_DEPARTMENT_DELETE = "SMS_DEPARTMENT_DELETE"; + public static final String SMS_DEPARTMENT_EXPORT = "SMS_DEPARTMENT_EXPORT"; + + // 工作组级别权限 + public static final String SMS_WORKGROUP_CREATE = "SMS_WORKGROUP_CREATE"; + public static final String SMS_WORKGROUP_READ = "SMS_WORKGROUP_READ"; + public static final String SMS_WORKGROUP_UPDATE = "SMS_WORKGROUP_UPDATE"; + public static final String SMS_WORKGROUP_DELETE = "SMS_WORKGROUP_DELETE"; + public static final String SMS_WORKGROUP_EXPORT = "SMS_WORKGROUP_EXPORT"; + + // 客服级别权限 + public static final String SMS_AGENT_CREATE = "SMS_AGENT_CREATE"; + public static final String SMS_AGENT_READ = "SMS_AGENT_READ"; + public static final String SMS_AGENT_UPDATE = "SMS_AGENT_UPDATE"; + public static final String SMS_AGENT_DELETE = "SMS_AGENT_DELETE"; + public static final String SMS_AGENT_EXPORT = "SMS_AGENT_EXPORT"; + // 用户级权限 + public static final String SMS_USER_READ = "SMS_USER_READ"; + public static final String SMS_USER_CREATE = "SMS_USER_CREATE"; + public static final String SMS_USER_UPDATE = "SMS_USER_UPDATE"; + public static final String SMS_USER_DELETE = "SMS_USER_DELETE"; + public static final String SMS_USER_EXPORT = "SMS_USER_EXPORT"; + + + // PreAuthorize注解使用的SpEL表达式 - 任意级别权限检查 + public static final String HAS_SMS_CREATE_ANY_LEVEL = "hasAnyAuthority('SMS_PLATFORM_CREATE', 'SMS_ORGANIZATION_CREATE', 'SMS_DEPARTMENT_CREATE', 'SMS_WORKGROUP_CREATE', 'SMS_AGENT_CREATE', 'SMS_USER_CREATE')"; + public static final String HAS_SMS_READ_ANY_LEVEL = "hasAnyAuthority('SMS_PLATFORM_READ', 'SMS_ORGANIZATION_READ', 'SMS_DEPARTMENT_READ', 'SMS_WORKGROUP_READ', 'SMS_AGENT_READ', 'SMS_USER_READ')"; + public static final String HAS_SMS_UPDATE_ANY_LEVEL = "hasAnyAuthority('SMS_PLATFORM_UPDATE', 'SMS_ORGANIZATION_UPDATE', 'SMS_DEPARTMENT_UPDATE', 'SMS_WORKGROUP_UPDATE', 'SMS_AGENT_UPDATE', 'SMS_USER_UPDATE')"; + public static final String HAS_SMS_DELETE_ANY_LEVEL = "hasAnyAuthority('SMS_PLATFORM_DELETE', 'SMS_ORGANIZATION_DELETE', 'SMS_DEPARTMENT_DELETE', 'SMS_WORKGROUP_DELETE', 'SMS_AGENT_DELETE', 'SMS_USER_DELETE')"; + public static final String HAS_SMS_EXPORT_ANY_LEVEL = "hasAnyAuthority('SMS_PLATFORM_EXPORT', 'SMS_ORGANIZATION_EXPORT', 'SMS_DEPARTMENT_EXPORT', 'SMS_WORKGROUP_EXPORT', 'SMS_AGENT_EXPORT', 'SMS_USER_EXPORT')"; +} diff --git a/modules/core/src/main/java/com/bytedesk/core/sms/SmsRepository.java b/modules/core/src/main/java/com/bytedesk/core/sms/SmsRepository.java new file mode 100644 index 0000000000..e2758dc1b2 --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/sms/SmsRepository.java @@ -0,0 +1,30 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2024-05-11 18:25:55 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-06-20 12:52:47 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * 联系:270580156@qq.com + * Copyright (c) 2024 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.sms; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +public interface SmsRepository extends JpaRepository, JpaSpecificationExecutor { + + Optional findByUid(String uid); + + Boolean existsByUid(String uid); + + Optional findByNameAndOrgUidAndTypeAndDeletedFalse(String name, String orgUid, String type); + + // Boolean existsByPlatform(String platform); +} diff --git a/modules/core/src/main/java/com/bytedesk/core/sms/SmsRequest.java b/modules/core/src/main/java/com/bytedesk/core/sms/SmsRequest.java new file mode 100644 index 0000000000..f942da0ada --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/sms/SmsRequest.java @@ -0,0 +1,44 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2024-05-11 18:26:04 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-06-20 14:24:05 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * 联系:270580156@qq.com + * Copyright (c) 2024 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.sms; + +import com.bytedesk.core.base.BaseRequest; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = false) +@AllArgsConstructor +@NoArgsConstructor +public class SmsRequest extends BaseRequest { + + private static final long serialVersionUID = 1L; + + private String name; + + private String description; + + // @Builder.Default + // private String type = SmsTypeEnum.CUSTOMER.name(); + + private String color; + + private Integer order; +} diff --git a/modules/core/src/main/java/com/bytedesk/core/sms/SmsResponse.java b/modules/core/src/main/java/com/bytedesk/core/sms/SmsResponse.java new file mode 100644 index 0000000000..638ce86a16 --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/sms/SmsResponse.java @@ -0,0 +1,46 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2024-05-11 18:26:12 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-06-04 15:36:28 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * 联系:270580156@qq.com + * Copyright (c) 2024 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.sms; + + +import com.bytedesk.core.base.BaseResponse; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = true) +@AllArgsConstructor +@NoArgsConstructor +public class SmsResponse extends BaseResponse { + + private static final long serialVersionUID = 1L; + + private String name; + + private String description; + + private String type; + + private String color; + + private Integer order; + +} diff --git a/modules/core/src/main/java/com/bytedesk/core/sms/SmsRestController.java b/modules/core/src/main/java/com/bytedesk/core/sms/SmsRestController.java new file mode 100644 index 0000000000..2c3e0c27eb --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/sms/SmsRestController.java @@ -0,0 +1,123 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2024-05-11 18:25:36 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-08-20 17:05:57 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * 联系:270580156@qq.com + * Copyright (c) 2024 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.sms; + +import org.springframework.data.domain.Page; +import org.springframework.http.ResponseEntity; +// import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.context.annotation.Description; + +import com.bytedesk.core.annotation.ActionAnnotation; +import com.bytedesk.core.base.BaseRestController; +import com.bytedesk.core.utils.JsonResult; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; +import lombok.AllArgsConstructor; + +@RestController +@RequestMapping("/api/v1/sms") +@AllArgsConstructor +@Tag(name = "Sms Management", description = "Sms management APIs for organizing and categorizing content with smss") +@Description("Sms Management Controller - Content smsging and categorization APIs") +public class SmsRestController extends BaseRestController { + + private final SmsRestService smsRestService; + + @ActionAnnotation(title = "Sms", action = "组织查询", description = "query sms by org") + @Operation(summary = "Query Smss by Organization", description = "Retrieve smss for the current organization") + @Override + public ResponseEntity queryByOrg(SmsRequest request) { + + Page smss = smsRestService.queryByOrg(request); + + return ResponseEntity.ok(JsonResult.success(smss)); + } + + @ActionAnnotation(title = "Sms", action = "用户查询", description = "query sms by user") + @Operation(summary = "Query Smss by User", description = "Retrieve smss for the current user") + @Override + public ResponseEntity queryByUser(SmsRequest request) { + + Page smss = smsRestService.queryByUser(request); + + return ResponseEntity.ok(JsonResult.success(smss)); + } + + @ActionAnnotation(title = "Sms", action = "查询详情", description = "query sms by uid") + @Operation(summary = "Query Sms by UID", description = "Retrieve a specific sms by its unique identifier") + @Override + public ResponseEntity queryByUid(SmsRequest request) { + + SmsResponse sms = smsRestService.queryByUid(request); + + return ResponseEntity.ok(JsonResult.success(sms)); + } + + @ActionAnnotation(title = "Sms", action = "新建", description = "create sms") + @Operation(summary = "Create Sms", description = "Create a new sms") + @Override + // @PreAuthorize("hasAuthority('TAG_CREATE')") + public ResponseEntity create(SmsRequest request) { + + SmsResponse sms = smsRestService.create(request); + + return ResponseEntity.ok(JsonResult.success(sms)); + } + + @ActionAnnotation(title = "Sms", action = "更新", description = "update sms") + @Operation(summary = "Update Sms", description = "Update an existing sms") + @Override + // @PreAuthorize("hasAuthority('TAG_UPDATE')") + public ResponseEntity update(SmsRequest request) { + + SmsResponse sms = smsRestService.update(request); + + return ResponseEntity.ok(JsonResult.success(sms)); + } + + @ActionAnnotation(title = "Sms", action = "删除", description = "delete sms") + @Operation(summary = "Delete Sms", description = "Delete a sms") + @Override + // @PreAuthorize("hasAuthority('TAG_DELETE')") + public ResponseEntity delete(SmsRequest request) { + + smsRestService.delete(request); + + return ResponseEntity.ok(JsonResult.success()); + } + + @ActionAnnotation(title = "Sms", action = "导出", description = "export sms") + @Operation(summary = "Export Smss", description = "Export smss to Excel format") + @Override + // @PreAuthorize("hasAuthority('TAG_EXPORT')") + @GetMapping("/export") + public Object export(SmsRequest request, HttpServletResponse response) { + return exportTemplate( + request, + response, + smsRestService, + SmsExcel.class, + "Sms", + "sms" + ); + } + + + +} \ No newline at end of file diff --git a/modules/core/src/main/java/com/bytedesk/core/sms/SmsRestService.java b/modules/core/src/main/java/com/bytedesk/core/sms/SmsRestService.java new file mode 100644 index 0000000000..273e566f2f --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/sms/SmsRestService.java @@ -0,0 +1,194 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2024-05-11 18:25:45 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-08-22 07:04:17 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * 联系:270580156@qq.com + * Copyright (c) 2024 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.sms; + +import java.util.Optional; + +import org.modelmapper.ModelMapper; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.orm.ObjectOptimisticLockingFailureException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; +import com.bytedesk.core.base.BaseRestServiceWithExport; +import com.bytedesk.core.rbac.auth.AuthService; +import com.bytedesk.core.rbac.user.UserEntity; +import com.bytedesk.core.uid.UidUtils; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +@AllArgsConstructor +public class SmsRestService extends BaseRestServiceWithExport { + + private final SmsRepository smsRepository; + + private final ModelMapper modelMapper; + + private final UidUtils uidUtils; + + private final AuthService authService; + + @Override + protected Specification createSpecification(SmsRequest request) { + return SmsSpecification.search(request, authService); + } + + @Override + protected Page executePageQuery(Specification spec, Pageable pageable) { + return smsRepository.findAll(spec, pageable); + } + + @Cacheable(value = "sms", key = "#uid", unless="#result==null") + @Override + public Optional findByUid(String uid) { + return smsRepository.findByUid(uid); + } + + @Cacheable(value = "sms", key = "#name + '_' + #orgUid + '_' + #type", unless="#result==null") + public Optional findByNameAndOrgUidAndType(String name, String orgUid, String type) { + return smsRepository.findByNameAndOrgUidAndTypeAndDeletedFalse(name, orgUid, type); + } + + public Boolean existsByUid(String uid) { + return smsRepository.existsByUid(uid); + } + + @Transactional + @Override + public SmsResponse create(SmsRequest request) { + // 判断是否已经存在 + if (StringUtils.hasText(request.getUid()) && existsByUid(request.getUid())) { + return convertToResponse(findByUid(request.getUid()).get()); + } + // 检查name+orgUid+type是否已经存在 + if (StringUtils.hasText(request.getName()) && StringUtils.hasText(request.getOrgUid()) && StringUtils.hasText(request.getType())) { + Optional sms = findByNameAndOrgUidAndType(request.getName(), request.getOrgUid(), request.getType()); + if (sms.isPresent()) { + return convertToResponse(sms.get()); + } + } + // + UserEntity user = authService.getUser(); + if (user != null) { + request.setUserUid(user.getUid()); + } + // + SmsEntity entity = modelMapper.map(request, SmsEntity.class); + if (!StringUtils.hasText(request.getUid())) { + entity.setUid(uidUtils.getUid()); + } + // + SmsEntity savedEntity = save(entity); + if (savedEntity == null) { + throw new RuntimeException("Create sms failed"); + } + return convertToResponse(savedEntity); + } + + @Transactional + @Override + public SmsResponse update(SmsRequest request) { + Optional optional = smsRepository.findByUid(request.getUid()); + if (optional.isPresent()) { + SmsEntity entity = optional.get(); + modelMapper.map(request, entity); + // + SmsEntity savedEntity = save(entity); + if (savedEntity == null) { + throw new RuntimeException("Update sms failed"); + } + return convertToResponse(savedEntity); + } + else { + throw new RuntimeException("Sms not found"); + } + } + + @Override + protected SmsEntity doSave(SmsEntity entity) { + return smsRepository.save(entity); + } + + @Override + public SmsEntity handleOptimisticLockingFailureException(ObjectOptimisticLockingFailureException e, SmsEntity entity) { + try { + Optional latest = smsRepository.findByUid(entity.getUid()); + if (latest.isPresent()) { + SmsEntity latestEntity = latest.get(); + // 合并需要保留的数据 + latestEntity.setName(entity.getName()); + // latestEntity.setOrder(entity.getOrder()); + // latestEntity.setDeleted(entity.isDeleted()); + return smsRepository.save(latestEntity); + } + } catch (Exception ex) { + log.error("无法处理乐观锁冲突: {}", ex.getMessage(), ex); + throw new RuntimeException("无法处理乐观锁冲突: " + ex.getMessage(), ex); + } + return null; + } + + @Transactional + @Override + public void deleteByUid(String uid) { + Optional optional = smsRepository.findByUid(uid); + if (optional.isPresent()) { + optional.get().setDeleted(true); + save(optional.get()); + // smsRepository.delete(optional.get()); + } + else { + throw new RuntimeException("Sms not found"); + } + } + + @Override + public void delete(SmsRequest request) { + deleteByUid(request.getUid()); + } + + @Override + public SmsResponse convertToResponse(SmsEntity entity) { + return modelMapper.map(entity, SmsResponse.class); + } + + @Override + public SmsExcel convertToExcel(SmsEntity entity) { + return modelMapper.map(entity, SmsExcel.class); + } + + public void initSmss(String orgUid) { + // log.info("initThreadSms"); + // for (String sms : SmsInitData.getAllSmss()) { + // SmsRequest smsRequest = SmsRequest.builder() + // .uid(Utils.formatUid(orgUid, sms)) + // .name(sms) + // .order(0) + // .type(SmsTypeEnum.THREAD.name()) + // .level(LevelEnum.ORGANIZATION.name()) + // .platform(BytedeskConsts.PLATFORM_BYTEDESK) + // .orgUid(orgUid) + // .build(); + // create(smsRequest); + // } + } + + + +} diff --git a/modules/core/src/main/java/com/bytedesk/core/sms/SmsSendResult.java b/modules/core/src/main/java/com/bytedesk/core/sms/SmsSendResult.java new file mode 100644 index 0000000000..c97f1b3931 --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/sms/SmsSendResult.java @@ -0,0 +1,66 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2025-12-05 10:00:00 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-12-05 10:00:00 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * 联系:270580156@qq.com + * Copyright (c) 2024 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.sms; + +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * 短信发送结果 + */ +@Data +@AllArgsConstructor +public class SmsSendResult { + + /** + * 是否成功 + */ + private boolean success; + + /** + * 错误类型 + */ + private SendCodeErrorType errorType; + + /** + * 错误信息 + */ + private String errorMessage; + + /** + * 成功结果 + */ + public static SmsSendResult success() { + return new SmsSendResult(true, null, null); + } + + /** + * 失败结果 + */ + public static SmsSendResult failure(SendCodeErrorType errorType, String errorMessage) { + return new SmsSendResult(false, errorType, errorMessage); + } + + /** + * 短信发送错误类型枚举 + */ + public enum SendCodeErrorType { + /** 发送过于频繁 */ + TOO_FREQUENT, + /** 验证码已发送 */ + ALREADY_SENT, + /** 发送失败 */ + SEND_FAILED + } +} diff --git a/modules/core/src/main/java/com/bytedesk/core/sms/SmsSendService.java b/modules/core/src/main/java/com/bytedesk/core/sms/SmsSendService.java new file mode 100644 index 0000000000..e0342dcc9f --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/sms/SmsSendService.java @@ -0,0 +1,273 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2024-03-31 15:29:55 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-12-05 10:00:00 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * + * Copyright (c) 2025 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.sms; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import com.aliyuncs.CommonRequest; +import com.aliyuncs.CommonResponse; +import com.aliyuncs.DefaultAcsClient; +import com.aliyuncs.IAcsClient; +import com.aliyuncs.exceptions.ClientException; +import com.aliyuncs.exceptions.ServerException; +import com.aliyuncs.http.MethodType; +import com.aliyuncs.profile.DefaultProfile; +import com.bytedesk.core.config.properties.BytedeskProperties; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.Assert; + +/** + * 短信发送服务 + */ +@Slf4j +@Service +public class SmsSendService { + + @Value("${aliyun.region.id:cn-hangzhou}") + private String regionId; + + @Value("${aliyun.access.key.id:}") + private String accessKeyId; + + @Value("${aliyun.access.key.secret:}") + private String accessKeySecret; + + @Value("${aliyun.sms.signname:}") + private String signName; + + @Value("${aliyun.sms.templatecode:}") + private String templateCode; + + /** + * 初始化时处理配置项编码问题 + */ + @Autowired + public void init() { + try { + // 检查签名是否为乱码,如果是则进行转换 + if (signName != null && !signName.isEmpty()) { + boolean needsConversion = false; + for (char c : signName.toCharArray()) { + if (c > 0x7F) { // 非ASCII字符 + needsConversion = true; + break; + } + } + + if (needsConversion) { + signName = new String(signName.getBytes("ISO-8859-1"), "UTF-8"); + log.info("短信签名编码转换完成: {}", signName); + } + } + } catch (Exception e) { + log.error("短信签名编码转换失败", e); + } + } + + @Autowired + private BytedeskProperties bytedeskProperties; + + private final ObjectMapper objectMapper = new ObjectMapper(); + + /** + * 发送短信 + * @param mobile 手机号 + * @param country 国家代码 + * @param content 短信内容 + * @param request HTTP请求 + * @return 是否发送成功 + */ + public boolean sendSms(String mobile, String country, String content, HttpServletRequest request) { + SmsSendResult result = sendSmsWithResult(mobile, country, content, request); + return result.isSuccess(); + } + + /** + * 发送短信并返回详细结果 + * @param mobile 手机号 + * @param country 国家代码 + * @param content 短信内容 + * @param request HTTP请求 + * @return SmsSendResult 发送结果 + */ + public SmsSendResult sendSmsWithResult(String mobile, String country, String content, HttpServletRequest request) { + Assert.hasText(mobile, "手机号不能为空"); + Assert.hasText(content, "短信内容不能为空"); + + log.info("send sms to {}, country: {}, content: {}", mobile, country, content); + + // 白名单手机号使用固定验证码,无需真正发送验证码 + if (bytedeskProperties.isInWhitelist(mobile)) { + return SmsSendResult.success(); // 白名单手机号认为发送成功 + } + + try { + return sendValidateCode(mobile, country, content); + } catch (Exception e) { + log.error("发送短信失败", e); + return SmsSendResult.failure(SmsSendResult.SendCodeErrorType.SEND_FAILED, "发送短信异常: " + e.getMessage()); + } + } + + /** + * 发送验证码 + * @param mobile 手机号 + * @param country 国家代码 + * @param code 验证码 + * @return SmsSendResult 发送结果 + */ + public SmsSendResult sendValidateCode(String mobile, String country, String code) { + Assert.hasText(mobile, "手机号不能为空"); + Assert.hasText(code, "验证码不能为空"); + + log.info("sendValidateCode sms to {}, country: {}, code: {}", mobile, country, code); + + // 处理国家代码:只保留数字,中国86可以不添加前缀 + String phoneNumber = formatPhoneNumber(mobile, country); + log.debug("格式化后的手机号: {}", phoneNumber); + + DefaultProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret); + IAcsClient client = new DefaultAcsClient(profile); + + CommonRequest request = new CommonRequest(); + request.setSysMethod(MethodType.POST); + request.setSysDomain("dysmsapi.aliyuncs.com"); + request.setSysVersion("2017-05-25"); + request.setSysAction("SendSms"); + request.putQueryParameter("RegionId", regionId); + request.putQueryParameter("PhoneNumbers", phoneNumber); + // 已在init方法中处理了编码问题,此处直接使用 + log.debug("配置文件签名:{}", signName); + request.putQueryParameter("SignName", signName); + request.putQueryParameter("TemplateCode", templateCode); + request.putQueryParameter("TemplateParam", "{\"code\":\"" + code + "\"}"); + try { + CommonResponse response = client.getCommonResponse(request); + // 发送失败提示:{"Message":"手机号码格式错误","RequestId":"42DC3C7D-DABE-5E13-AB10-873060508C47","Code":"isv.MOBILE_NUMBER_ILLEGAL"} + // 发送成功提示:{"Message":"OK","RequestId":"1EA51590-4DBF-51EC-9FEC-812E7193C74D","Code":"OK","BizId":"458315458265098373^0"} + log.info("sendValidateCode sms response: {}", response.getData()); + + // 解析响应结果 + return parseAliyunSmsResponse(response.getData()); + } catch (ServerException e) { + log.error("阿里云短信发送失败 - ServerException", e); + return SmsSendResult.failure(SmsSendResult.SendCodeErrorType.SEND_FAILED, "阿里云服务器错误: " + e.getErrMsg()); + } catch (ClientException e) { + log.error("阿里云短信发送失败 - ClientException", e); + return SmsSendResult.failure(SmsSendResult.SendCodeErrorType.SEND_FAILED, "阿里云客户端错误: " + e.getErrMsg()); + } + } + + /** + * 格式化手机号码,处理国家代码 + * @param mobile 手机号 + * @param country 国家代码 + * @return 格式化后的手机号 + */ + private String formatPhoneNumber(String mobile, String country) { + // 处理国家代码:只保留数字 + String cleanCountry = ""; + if (country != null && !country.isEmpty()) { + cleanCountry = country.replaceAll("[^0-9]", ""); + } + + // 中国86区号可以不添加前缀 + if ("86".equals(cleanCountry)) { + return mobile; + } + + // 其他国家需要添加国家代码前缀 + if (!cleanCountry.isEmpty()) { + return cleanCountry + mobile; + } + + // 如果没有国家代码,默认返回手机号(中国手机号) + return mobile; + } + + /** + * 解析阿里云短信服务响应 + * @param responseData 响应JSON数据 + * @return SmsSendResult + */ + private SmsSendResult parseAliyunSmsResponse(String responseData) { + try { + JsonNode jsonNode = objectMapper.readTree(responseData); + String code = jsonNode.get("Code").asText(); + String message = jsonNode.get("Message").asText(); + + // 判断是否发送成功 + if ("OK".equalsIgnoreCase(code)) { + return SmsSendResult.success(); + } else { + // 根据错误代码返回中文错误信息 + String errorMessage = getChineseErrorMessage(code, message); + return SmsSendResult.failure(SmsSendResult.SendCodeErrorType.SEND_FAILED, errorMessage); + } + } catch (Exception e) { + log.error("解析阿里云短信响应失败", e); + return SmsSendResult.failure(SmsSendResult.SendCodeErrorType.SEND_FAILED, "解析短信服务响应失败"); + } + } + + /** + * 根据阿里云错误代码返回中文错误信息 + * @param code 错误代码 + * @param originalMessage 原始错误信息 + * @return 中文错误信息 + */ + private String getChineseErrorMessage(String code, String originalMessage) { + switch (code) { + case "isv.MOBILE_NUMBER_ILLEGAL": + return "手机号码格式错误"; + case "isv.MOBILE_COUNT_OVER_LIMIT": + return "手机号码数量超过限制"; + case "isv.TEMPLATE_MISSING_PARAMETERS": + return "短信模板参数缺失"; + case "isv.BUSINESS_LIMIT_CONTROL": + return "业务限流"; + case "isv.INVALID_PARAMETERS": + return "参数错误"; + case "isv.SYSTEM_ERROR": + return "系统错误"; + case "isv.OUT_OF_SERVICE": + return "服务不可用"; + case "SignatureNonce.Duplicate": + return "重复请求"; + case "InvalidTimeStamp.Expired": + return "时间戳过期"; + case "SignatureDoesNotMatch": + return "签名验证失败"; + case "InvalidAccessKeyId.NotFound": + return "AccessKey不存在"; + case "Forbidden.RAM": + return "RAM权限不足"; + case "isv.DAY_LIMIT_CONTROL": + return "日发送量超限"; + case "isv.SMS_CONTENT_ILLEGAL": + return "短信内容包含违禁词"; + case "isv.SMS_SIGN_ILLEGAL": + return "短信签名不合规"; + default: + return "短信发送失败: " + originalMessage; + } + } + +} diff --git a/modules/core/src/main/java/com/bytedesk/core/sms/SmsSpecification.java b/modules/core/src/main/java/com/bytedesk/core/sms/SmsSpecification.java new file mode 100644 index 0000000000..d1fdce3007 --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/sms/SmsSpecification.java @@ -0,0 +1,56 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2024-07-09 22:19:21 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-08-18 15:44:34 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * 联系:270580156@qq.com + * Copyright (c) 2024 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.sms; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.data.jpa.domain.Specification; +import org.springframework.util.StringUtils; + +import com.bytedesk.core.base.BaseSpecification; +import com.bytedesk.core.rbac.auth.AuthService; +import jakarta.persistence.criteria.Predicate; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class SmsSpecification extends BaseSpecification { + + public static Specification search(SmsRequest request, AuthService authService) { + // log.info("request: {} orgUid: {} pageNumber: {} pageSize: {}", + // request, request.getOrgUid(), request.getPageNumber(), request.getPageSize()); + return (root, query, criteriaBuilder) -> { + List predicates = new ArrayList<>(); + predicates.addAll(getBasicPredicates(root, criteriaBuilder, request, authService)); + // name + if (StringUtils.hasText(request.getName())) { + predicates.add(criteriaBuilder.like(root.get("name"), "%" + request.getName() + "%")); + } + // description + if (StringUtils.hasText(request.getDescription())) { + predicates.add(criteriaBuilder.like(root.get("description"), "%" + request.getDescription() + "%")); + } + // type + if (StringUtils.hasText(request.getType())) { + predicates.add(criteriaBuilder.equal(root.get("type"), request.getType())); + } + // + if (StringUtils.hasText(request.getUserUid())) { + predicates.add(criteriaBuilder.equal(root.get("userUid"), request.getUserUid())); + } + // + return criteriaBuilder.and(predicates.toArray(new Predicate[0])); + }; + } +} diff --git a/modules/core/src/main/java/com/bytedesk/core/sms/SmsTypeEnum.java b/modules/core/src/main/java/com/bytedesk/core/sms/SmsTypeEnum.java new file mode 100644 index 0000000000..995427e92d --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/sms/SmsTypeEnum.java @@ -0,0 +1,20 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2024-07-23 17:02:46 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-03-11 08:57:11 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * 联系:270580156@qq.com + * Copyright (c) 2024 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.sms; + +public enum SmsTypeEnum { + THREAD, + CUSTOMER, + TICKET +} diff --git a/modules/core/src/main/java/com/bytedesk/core/sms/event/SmsCreateEvent.java b/modules/core/src/main/java/com/bytedesk/core/sms/event/SmsCreateEvent.java new file mode 100644 index 0000000000..f64caa361f --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/sms/event/SmsCreateEvent.java @@ -0,0 +1,36 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2025-02-25 09:59:29 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-02-25 10:00:34 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * + * Copyright (c) 2025 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.sms.event; + +import org.springframework.context.ApplicationEvent; + +import com.bytedesk.core.sms.SmsEntity; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = false) +public class SmsCreateEvent extends ApplicationEvent { + + private static final long serialVersionUID = 1L; + + private SmsEntity sms; + + public SmsCreateEvent(SmsEntity sms) { + super(sms); + this.sms = sms; + } + +} diff --git a/modules/core/src/main/java/com/bytedesk/core/sms/event/SmsDeleteEvent.java b/modules/core/src/main/java/com/bytedesk/core/sms/event/SmsDeleteEvent.java new file mode 100644 index 0000000000..878fc65c5c --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/sms/event/SmsDeleteEvent.java @@ -0,0 +1,35 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2025-02-25 12:31:16 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-02-25 12:31:19 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * + * Copyright (c) 2025 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.sms.event; + +import org.springframework.context.ApplicationEvent; + +import com.bytedesk.core.sms.SmsEntity; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = false) +public class SmsDeleteEvent extends ApplicationEvent { + + private static final long serialVersionUID = 1L; + + private SmsEntity sms; + + public SmsDeleteEvent(SmsEntity sms) { + super(sms); + this.sms = sms; + } +} diff --git a/modules/core/src/main/java/com/bytedesk/core/sms/event/SmsUpdateEvent.java b/modules/core/src/main/java/com/bytedesk/core/sms/event/SmsUpdateEvent.java new file mode 100644 index 0000000000..61fa0df6b6 --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/sms/event/SmsUpdateEvent.java @@ -0,0 +1,36 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2025-02-25 09:59:29 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-02-25 10:01:00 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * + * Copyright (c) 2025 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.core.sms.event; + +import org.springframework.context.ApplicationEvent; + +import com.bytedesk.core.sms.SmsEntity; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = false) +public class SmsUpdateEvent extends ApplicationEvent { + + private static final long serialVersionUID = 1L; + + private SmsEntity sms; + + public SmsUpdateEvent(SmsEntity sms) { + super(sms); + this.sms = sms; + } + +} diff --git a/modules/core/src/main/java/com/bytedesk/core/sms/package-info.java b/modules/core/src/main/java/com/bytedesk/core/sms/package-info.java new file mode 100644 index 0000000000..01400fc02e --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/sms/package-info.java @@ -0,0 +1,5 @@ + +@NonNullApi +package com.bytedesk.core.sms; + +import org.springframework.lang.NonNullApi; diff --git a/modules/core/src/main/java/com/bytedesk/core/sms/readme.md b/modules/core/src/main/java/com/bytedesk/core/sms/readme.md new file mode 100644 index 0000000000..f266b3b2a7 --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/sms/readme.md @@ -0,0 +1 @@ +# 短信发送记录 diff --git a/modules/core/src/main/java/com/bytedesk/core/sms/readme.zh.md b/modules/core/src/main/java/com/bytedesk/core/sms/readme.zh.md new file mode 100644 index 0000000000..689f209d19 --- /dev/null +++ b/modules/core/src/main/java/com/bytedesk/core/sms/readme.zh.md @@ -0,0 +1,3 @@ +# 短信发送记录 + +- TODO: \ No newline at end of file diff --git a/modules/core/src/main/java/com/bytedesk/core/utils/Utils.java b/modules/core/src/main/java/com/bytedesk/core/utils/Utils.java index e8602936df..b2cb0ce70e 100644 --- a/modules/core/src/main/java/com/bytedesk/core/utils/Utils.java +++ b/modules/core/src/main/java/com/bytedesk/core/utils/Utils.java @@ -79,9 +79,9 @@ public class Utils { * @param mobile * @return */ - public static boolean isTestMobile(String mobile) { - return mobile.startsWith("188"); - } + // public static boolean isTestMobile(String mobile) { + // return mobile.startsWith("188"); + // } public static boolean isTestEmail(String email) { return email.endsWith("@email.com");