From 3aab5b9c75a0e933906ca82570e02db505740741 Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Tue, 16 Dec 2025 10:50:58 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E6=9B=B4=E6=96=B0=E4=BC=81=E4=B8=9A?= =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E9=85=8D=E7=BD=AE=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/model/web/Config.php | 18 + src/app/shop/controller/Config.php | 1 + src/app/shop/view/config/wxwork.html | 478 ++++++++++++++++++++++++--- 3 files changed, 442 insertions(+), 55 deletions(-) diff --git a/src/app/model/web/Config.php b/src/app/model/web/Config.php index bed6c07bc..c5969eb0c 100644 --- a/src/app/model/web/Config.php +++ b/src/app/model/web/Config.php @@ -856,6 +856,23 @@ class Config extends BaseModel */ public function setWxworkConfig($data, $site_id = 1, $app_module = 'shop') { + // 只要提供了Secret,就自动生成时间戳、随机字符串和签名 + if (!empty($data['secret'])) { + $data['timestamp'] = time(); + + // 生成16位随机字符串 + $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $nonceStr = ''; + for ($i = 0; $i < 16; $i++) { + $nonceStr .= substr($chars, mt_rand(0, strlen($chars) - 1), 1); + } + $data['nonceStr'] = $nonceStr; + + // 生成SHA1签名 + $string = 'secret=' . $data['secret'] . '×tamp=' . $data['timestamp'] . '&nonceStr=' . $data['nonceStr']; + $data['signature'] = sha1($string); + } + $config = new ConfigModel(); $res = $config->setConfig($data, '企业微信配置', 1, [ [ 'site_id', '=', $site_id ], [ 'app_module', '=', $app_module ], [ 'config_key', '=', 'WXWORK_CONFIG' ] ]); return $res; @@ -875,6 +892,7 @@ class Config extends BaseModel $res[ 'data' ][ 'value' ] = [ 'corp_id' => '', 'agent_id' => '', + 'secret' => '', 'contact_id' => '', 'contact_url' => '', 'timestamp' => '', diff --git a/src/app/shop/controller/Config.php b/src/app/shop/controller/Config.php index da961574f..f79f6c1a3 100644 --- a/src/app/shop/controller/Config.php +++ b/src/app/shop/controller/Config.php @@ -220,6 +220,7 @@ class Config extends BaseShop $data = [ 'corp_id' => input('corp_id', ''), 'agent_id' => input('agent_id', ''), + 'secret' => input('secret', ''), 'contact_id' => input('contact_id', ''), 'contact_url' => input('contact_url', ''), 'timestamp' => input('timestamp', ''), diff --git a/src/app/shop/view/config/wxwork.html b/src/app/shop/view/config/wxwork.html index 157f19602..08eb3dd1c 100644 --- a/src/app/shop/view/config/wxwork.html +++ b/src/app/shop/view/config/wxwork.html @@ -3,9 +3,30 @@ .wxwork-form .layui-form-item{margin-bottom: 20px;} .wxwork-form .layui-input-block{margin-left: 120px;} .wxwork-help{color: #999; font-size: 12px; margin-top: 5px;} + .wxwork-required{color: #ff5722; margin-right: 4px;} + .wxwork-guide{background: #f8f9fa; border-left: 3px solid #007bff; padding: 15px; margin-top: 10px; border-radius: 4px;} + .wxwork-guide h4{margin: 0 0 10px 0; color: #007bff;} + .wxwork-guide ol{margin: 0; padding-left: 20px;} + .wxwork-guide li{margin-bottom: 8px;} + .wxwork-code{background: #f1f3f4; padding: 2px 6px; border-radius: 3px; font-family: monospace; font-size: 12px;} + .wxwork-field-required .layui-form-label{color: #333;} + .wxwork-link{color: #007bff; text-decoration: none;} + .wxwork-link:hover{text-decoration: underline;} + .wxwork-guide-toggle{margin-top: 30px;} + .wxwork-guide-toggle .guide-toggle-btn{width: 100%; padding: 12px; background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 4px; cursor: pointer; transition: all 0.3s; display: flex; align-items: center; justify-content: center; gap: 8px; font-size: 14px; color: #333;} + .wxwork-guide-toggle .guide-toggle-btn:hover{background: #e9ecef; border-color: #007bff;} + .wxwork-guide-toggle .guide-toggle-btn.active{background: #007bff; color: white; border-color: #007bff;} + .wxwork-guide-toggle .guide-content{margin-top: 15px; background: #f8f9fa; border-left: 3px solid #007bff; padding: 15px; border-radius: 4px; display: none;} + .wxwork-guide-toggle .guide-content.show{display: block;} + .guide-toggle-icon{transition: transform 0.3s;} + .guide-toggle-icon.rotate{transform: rotate(180deg);} + .password-wrapper{position: relative;} + .password-toggle{position: absolute; right: 10px; top: 50%; transform: translateY(-50%); cursor: pointer; color: #999; font-size: 16px; user-select: none;} + .password-toggle:hover{color: #007bff;}
+
@@ -14,79 +35,182 @@
开启后,企业微信相关功能将可用
-
- +
+
- +
-

企业微信的企业ID,可在企业微信管理后台获取

+

获取方式:企业微信管理后台 → 我的企业 → 企业信息 → 企业ID

+

格式:通常以 "ww" 开头的18位字符串

-
- +
+
- +
-

企业微信应用的AgentId,创建应用后获取

+

获取方式:应用管理 → 选择应用 → 查看应用详情 → AgentId

+

格式:数字字符串,如 1000001

-
- +
+
- +
+ + 👁️ +
-
-

企业微信客服的ID,用于客服功能

+
+

获取方式:企业微信管理后台 → 应用管理 → 选择应用 → 查看Secret

+

说明系统必需:用于自动生成签名,请妥善保管

+

安全提示:Secret仅在创建时可见,请立即复制保存

-
- +
+
- +
-
-

企业微信活码的链接地址

+
+

获取方式:企业微信管理后台 → 应用管理 → 客服 → 联系我

+

说明系统必需功能,用于企业微信客服对接

+

格式:通常以 "kfc_" 开头的字符串

-
- +
+
- +
-
-

用于验证的时间戳,通常由系统自动生成

+
+

获取方式:企业微信管理后台 → 客服 → 联系我 → 生成活码

+

说明系统必需功能,客服活码的完整链接地址

+

格式:完整的HTTPS链接,如 https://work.weixin.qq.com/kf/xxxx

-
- -
- -
-
-

用于验证的随机字符串

-
-
+ + + + +
-
- +
+
+ 配置完整性: + 检查中... +
+
+ 签名生成状态: + 点击保存配置,将自动生成 +
+
+ 时间戳: + {if condition="$wxwork_config.timestamp"}{$wxwork_config.timestamp}{else /}未生成{/if} +
+
+ 随机字符串: + {if condition="$wxwork_config.nonceStr"}{$wxwork_config.nonceStr}{else /}未生成{/if} +
+
+ 签名: + {if condition="$wxwork_config.signature"}{$wxwork_config.signature}{else /}未生成{/if} +
+
+ 最后更新时间: + {if condition="$wxwork_config.timestamp"}{else /}从未生成{/if} +
+
-

用于验证的签名,通常由系统自动生成

+

说明:系统将在保存配置时自动生成时间戳、随机字符串和签名

+

生成规则:基于Secret使用SHA1算法生成,用于企业微信API验证

- + + +
+
+ + +
+
+ 📋 企业微信配置获取指南 + +
+
+

⚠️ 注意:客服功能为系统必需功能,必须正确配置相关参数

+
    +
  1. 登录企业微信管理后台https://work.weixin.qq.com/
  2. +
  3. 获取企业ID:进入「我的企业」→「企业信息」→「企业ID」
  4. +
  5. 创建应用:进入「应用管理」→「应用」→「创建应用」→「自建」
  6. +
  7. 获取应用ID:创建完成后进入应用详情页,查看 AgentId
  8. +
  9. 获取Secret:在应用详情页查看 Secret(用于生成签名)
  10. +
  11. 配置可信域名:在应用详情页设置可信域名,用于JS接口调用
  12. +
  13. 开启客服功能(必需)进入「应用管理」→「客服」→「联系我」→ 开启服务
  14. +
  15. 获取客服参数:在客服页面获取客服ID和活码链接
  16. +
+ +
🔧 参数获取方式说明
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
参数名称获取方式说明
企业ID企业微信管理后台 → 我的企业 → 企业信息 → 企业ID企业唯一标识
应用ID企业微信管理后台 → 应用管理 → 选择应用 → 查看应用详情 → AgentId应用的AgentId
应用Secret企业微信管理后台 → 应用管理 → 选择应用 → 查看Secret必需:用于生成签名的密钥
客服ID企业微信管理后台 → 应用管理 → 客服 → 联系我必需:客服功能的唯一标识
活码链接企业微信管理后台 → 客服 → 联系我 → 生成活码必需:客服活码的完整链接
时间戳本系统自动生成Unix时间戳,无需手动填写
随机字符串本系统自动生成16位随机字符,无需手动填写
签名本系统自动生成SHA1签名,无需手动填写
@@ -98,10 +222,233 @@ form.render(); - /** - * 监听提交 - */ + + + // 启用状态切换时的验证 + $('input[name="enabled"]').on('change', function() { + if($(this).is(':checked')) { + var corpId = $('input[name="corp_id"]').val().trim(); + var agentId = $('input[name="agent_id"]').val().trim(); + var secret = $('input[name="secret"]').val().trim(); + var contactId = $('input[name="contact_id"]').val().trim(); + var contactUrl = $('input[name="contact_url"]').val().trim(); + + if(!corpId) { + layer.msg('请先填写企业ID(必填项)', {icon: 2}); + $(this).prop('checked', false); + form.render(); + $('input[name="corp_id"]').focus(); + return; + } + + if(!agentId) { + layer.msg('请先填写应用ID(必填项)', {icon: 2}); + $(this).prop('checked', false); + form.render(); + $('input[name="agent_id"]').focus(); + return; + } + + if(!secret) { + layer.msg('请先填写应用Secret(系统必需)', {icon: 2}); + $(this).prop('checked', false); + form.render(); + $('input[name="secret"]').focus(); + return; + } + + if(!contactId) { + layer.msg('请先填写客服ID(系统必需功能)', {icon: 2}); + $(this).prop('checked', false); + form.render(); + $('input[name="contact_id"]').focus(); + return; + } + + if(!contactUrl) { + layer.msg('请先填写活码链接(系统必需功能)', {icon: 2}); + $(this).prop('checked', false); + form.render(); + $('input[name="contact_url"]').focus(); + return; + } + + // 企业ID格式验证 + if(!/^ww[a-zA-Z0-9]{16}$/.test(corpId)) { + layer.msg('企业ID格式不正确,应为ww开头的18位字符', {icon: 2}); + $(this).prop('checked', false); + form.render(); + $('input[name="corp_id"]').focus(); + return; + } + + // 应用ID格式验证 + if(!/^\d+$/.test(agentId)) { + layer.msg('应用ID格式不正确,应为纯数字', {icon: 2}); + $(this).prop('checked', false); + form.render(); + $('input[name="agent_id"]').focus(); + return; + } + + // Secret格式验证 + if(secret.length < 10) { + layer.msg('Secret格式不正确,长度至少10位字符', {icon: 2}); + $(this).prop('checked', false); + form.render(); + $('input[name="secret"]').focus(); + return; + } + + layer.msg('配置验证通过!企业微信功能已启用,系统将自动生成签名', {icon: 1}); + } + }); + + // 更新配置完整性状态 + function updateConfigStatus() { + var corpId = $('input[name="corp_id"]').val().trim(); + var agentId = $('input[name="agent_id"]').val().trim(); + var secret = $('input[name="secret"]').val().trim(); + var contactId = $('input[name="contact_id"]').val().trim(); + var contactUrl = $('input[name="contact_url"]').val().trim(); + var timestamp = $('input[name="timestamp"]').val(); + var nonceStr = $('input[name="nonceStr"]').val(); + var signature = $('input[name="signature"]').val(); + + // 更新配置完整性 + var requiredFields = [corpId, agentId, secret, contactId, contactUrl]; + var filledCount = requiredFields.filter(function(field) { return field.length > 0; }).length; + var completeness = Math.round((filledCount / requiredFields.length) * 100); + + var completenessElement = $('#config-completeness'); + if (completeness === 100) { + completenessElement.text('完整 (100%)').css('color', '#52c41a'); + } else { + completenessElement.text('不完整 (' + completeness + '%)').css('color', '#ff7875'); + } + + // 更新签名状态和相关信息 + var signatureElement = $('#signature-status'); + var timestampElement = $('#timestamp-value'); + var nonceStrElement = $('#nonce-str-value'); + var signatureValueElement = $('#signature-value'); + + if (signature && timestamp) { + // 显示已存在的签名信息 + signatureElement.text('已生成').css('color', '#52c41a'); + timestampElement.text(timestamp).css('color', '#52c41a'); + nonceStrElement.text(nonceStr || '未生成').css('color', '#52c41a'); + signatureValueElement.text(signature).css('color', '#52c41a'); + } else { + // 显示生成提示 + if (completeness === 100) { + signatureElement.text('配置完整,点击保存将生成').css('color', '#1890ff'); + } else { + signatureElement.text('请完善必填配置信息').css('color', '#ff7875'); + } + timestampElement.text('未生成').css('color', '#999'); + nonceStrElement.text('未生成').css('color', '#999'); + signatureValueElement.text('未生成').css('color', '#999'); + } + } + + // 监听所有输入框变化 + $('input[name="corp_id"], input[name="agent_id"], input[name="secret"], input[name="contact_id"], input[name="contact_url"]').on('input blur', function() { + updateConfigStatus(); + }); + + // 初始化状态 + updateConfigStatus(); + + // 表单提交前的必填项验证 form.on('submit(save)', function(data) { + var corpId = data.field.corp_id.trim(); + var agentId = data.field.agent_id.trim(); + var secret = data.field.secret.trim(); + var contactId = data.field.contact_id.trim(); + var contactUrl = data.field.contact_url.trim(); + + // 验证企业ID + if(!corpId) { + layer.msg('企业ID为必填项', {icon: 2}); + $('input[name="corp_id"]').focus(); + repeat_flag = false; + return false; + } + + // 验证应用ID + if(!agentId) { + layer.msg('应用ID为必填项', {icon: 2}); + $('input[name="agent_id"]').focus(); + repeat_flag = false; + return false; + } + + // 验证Secret(必需) + if(!secret) { + layer.msg('应用Secret为系统必需参数,必须填写', {icon: 2}); + $('input[name="secret"]').focus(); + repeat_flag = false; + return false; + } + + // 验证客服ID(必需) + if(!contactId) { + layer.msg('客服ID为系统必需功能,必须填写', {icon: 2}); + $('input[name="contact_id"]').focus(); + repeat_flag = false; + return false; + } + + // 验证活码链接(必需) + if(!contactUrl) { + layer.msg('活码链接为系统必需功能,必须填写', {icon: 2}); + $('input[name="contact_url"]').focus(); + repeat_flag = false; + return false; + } + + // 企业ID格式验证 + if(!/^ww[a-zA-Z0-9]{16}$/.test(corpId)) { + layer.msg('企业ID格式不正确,应为ww开头的18位字符', {icon: 2}); + $('input[name="corp_id"]').focus(); + repeat_flag = false; + return false; + } + + // 应用ID格式验证 + if(!/^\d+$/.test(agentId)) { + layer.msg('应用ID格式不正确,应为纯数字', {icon: 2}); + $('input[name="agent_id"]').focus(); + repeat_flag = false; + return false; + } + + // Secret格式验证 + if(secret.length < 10) { + layer.msg('Secret格式不正确,长度至少10位字符', {icon: 2}); + $('input[name="secret"]').focus(); + repeat_flag = false; + return false; + } + + // 客服ID格式验证 + if(!/^kfc_[a-zA-Z0-9]+$/.test(contactId)) { + layer.msg('客服ID格式不正确,应以kfc_开头', {icon: 2}); + $('input[name="contact_id"]').focus(); + repeat_flag = false; + return false; + } + + // 活码链接格式验证 + if(!/^https:\/\/work\.weixin\.qq\.com\/kf\//.test(contactUrl)) { + layer.msg('活码链接格式不正确,应为 https://work.weixin.qq.com/kf/... 格式', {icon: 2}); + $('input[name="contact_url"]').focus(); + repeat_flag = false; + return false; + } + + // 继续执行原有的AJAX提交逻辑 if (repeat_flag) return false; repeat_flag = true; @@ -115,7 +462,11 @@ type: 'POST', success: function(res){ if(res.code == 0){ - layer.msg(res.message, {icon: 1}); + layer.msg('企业微信配置保存成功!系统已自动生成签名和时间戳', {icon: 1}); + // 刷新页面以显示更新后的状态 + setTimeout(function() { + window.location.reload(); + }, 1500); }else{ layer.msg(res.message, {icon: 2}); } @@ -126,21 +477,38 @@ repeat_flag = false; } }); - }); - - // 启用状态切换时的提示 - $('input[name="enabled"]').on('change', function() { - if($(this).is(':checked')) { - // 可以在这里添加启用时的验证提示 - var corpId = $('input[name="corp_id"]').val(); - var agentId = $('input[name="agent_id"]').val(); - - if(!corpId || !agentId) { - layer.msg('请先填写企业ID和应用ID', {icon: 2}); - $(this).prop('checked', false); - form.render(); - } - } + return false; // 阻止默认提交 }); }); + + // 切换密码显示/隐藏 + function togglePassword() { + var secretInput = $('#secret-input'); + var toggleBtn = $('.password-toggle'); + + if (secretInput.attr('type') === 'password') { + secretInput.attr('type', 'text'); + toggleBtn.text('🙈'); + } else { + secretInput.attr('type', 'password'); + toggleBtn.text('👁️'); + } + } + + // 切换指南显示/隐藏 + function toggleGuide() { + var btn = $('.guide-toggle-btn'); + var content = $('.guide-content'); + var icon = $('.guide-toggle-icon'); + + if (content.hasClass('show')) { + content.removeClass('show'); + btn.removeClass('active'); + icon.removeClass('rotate'); + } else { + content.addClass('show'); + btn.addClass('active'); + icon.addClass('rotate'); + } + } \ No newline at end of file