From d1f994c3a56b66cfe453b5dfaaff81d90fac6590 Mon Sep 17 00:00:00 2001
From: wanglizhong <wlz>
Date: 星期四, 01 五月 2025 17:46:45 +0800
Subject: [PATCH] fix:增加 @anonymouse注解

---
 ruoyi-common/src/main/java/com/ruoyi/common/annotation/Anonymous.java                   |    5 
 ruoyi-system/src/main/java/com/ruoyi/system/domain/SysClientApp.java                    |    3 
 ruoyi-system/src/main/java/com/ruoyi/system/service/ISysClientAppService.java           |   18 +++
 ruoyi-system/src/main/resources/mapper/system/SysClientAppMapper.xml                    |    5 
 ruoyi-ui/src/views/system/clientApp/index.vue                                           |    8 +
 ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysClientAppMapper.java              |    8 +
 ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/AnonymousInterceptor.java |   68 +++++++++++
 ruoyi-common/src/main/java/com/ruoyi/common/utils/SecurityUtils.java                    |   26 ++++
 ruoyi-admin/src/main/resources/application.yml                                          |    2 
 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysClientAppServiceImpl.java   |   52 ++++++++
 sql/sql_client_app_menu.sql                                                             |   22 +++
 ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysClientAppController.java   |   36 ++++++
 ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java           |   16 +-
 ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java            |   54 +++++++-
 14 files changed, 303 insertions(+), 20 deletions(-)

diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysClientAppController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysClientAppController.java
index 95a3557..79c319b 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysClientAppController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysClientAppController.java
@@ -20,6 +20,8 @@
 import com.ruoyi.system.service.ISysClientAppService;
 import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.annotation.Anonymous;
+import com.ruoyi.common.utils.SecurityUtils;
 
 /**
  * 瀹㈡埛搴旂敤閰嶇疆Controller
@@ -95,5 +97,39 @@
     public AjaxResult remove(@PathVariable Long[] appIds) {
         return toAjax(sysClientAppService.deleteSysClientAppByAppIds(appIds));
     }
+
+    @Anonymous(needSign=true)
+    @GetMapping("/testSign")
+    public AjaxResult testSign(){
+        return AjaxResult.success("鎴愬姛");
+    }
+    /**
+     * 鐢熸垚绛惧悕
+     */
+    @Anonymous
+    @GetMapping("/generateSign/{appId}")
+    public AjaxResult generateSign(@PathVariable("appId") String appId)
+    {
+        // 鑾峰彇褰撳墠绯荤粺鏃堕棿鎴�
+        long timestamp = System.currentTimeMillis();
+        
+        // 鏌ヨ搴旂敤淇℃伅鑾峰彇securityKey
+        SysClientApp clientApp = sysClientAppService.selectSysClientAppByAppKey(appId);
+        if (clientApp == null)
+        {
+            return AjaxResult.error("搴旂敤涓嶅瓨鍦�");
+        }
+        
+        // 鐢熸垚绛惧悕
+        String signStr = appId + timestamp + clientApp.getSecurityKey();
+        String sign = SecurityUtils.md5(signStr);
+        
+        AjaxResult ajax = AjaxResult.success();
+        ajax.put("appId", appId);
+        ajax.put("timestamp", String.valueOf(timestamp));
+        ajax.put("sign", sign);
+        //ajax.put("signStr", signStr);  // 鐢ㄤ簬璋冭瘯锛屾樉绀烘嫾鎺ョ殑瀛楃涓�
+        return ajax;
+    }
 } 
  
\ No newline at end of file
diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml
index 43751f6..db0b9f1 100644
--- a/ruoyi-admin/src/main/resources/application.yml
+++ b/ruoyi-admin/src/main/resources/application.yml
@@ -74,7 +74,7 @@
     # 鍦板潃
     host: localhost
     # 绔彛锛岄粯璁や负6379
-    port: 16379
+    port: 6379
     # 鏁版嵁搴撶储寮�
     database: 0
     # 瀵嗙爜
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Anonymous.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Anonymous.java
index 1d6d4f4..749115d 100644
--- a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Anonymous.java
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Anonymous.java
@@ -16,4 +16,9 @@
 @Documented
 public @interface Anonymous
 {
+    /**
+     * 鏄惁闇�瑕佺鍚嶉獙璇佸強鏃堕棿鎴抽獙璇�
+     */
+    boolean needSign() default false;
+
 }
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/SecurityUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/SecurityUtils.java
index 0d3ac5f..f4a3eb3 100644
--- a/ruoyi-common/src/main/java/com/ruoyi/common/utils/SecurityUtils.java
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/SecurityUtils.java
@@ -1,5 +1,7 @@
 package com.ruoyi.common.utils;
 
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 import java.util.Collection;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -113,7 +115,29 @@
         BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
         return passwordEncoder.matches(rawPassword, encodedPassword);
     }
-
+    /**
+     * MD5鍔犲瘑
+     *
+     * @param str 闇�瑕佸姞瀵嗙殑瀛楃涓�
+     * @return 鍔犲瘑鍚庣殑瀛楃涓�
+     */
+    public static String md5(String str) {
+        try {
+            MessageDigest md = MessageDigest.getInstance("MD5");
+            byte[] bytes = md.digest(str.getBytes());
+            StringBuilder result = new StringBuilder();
+            for (byte b : bytes) {
+                String temp = Integer.toHexString(b & 0xff);
+                if (temp.length() == 1) {
+                    temp = "0" + temp;
+                }
+                result.append(temp);
+            }
+            return result.toString();
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("MD5鍔犲瘑澶辫触", e);
+        }
+    }
     /**
      * 鏄惁涓虹鐞嗗憳
      * 
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java
index 0f48b11..0eeab6e 100644
--- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java
@@ -14,6 +14,7 @@
 import com.ruoyi.common.config.RuoYiConfig;
 import com.ruoyi.common.constant.Constants;
 import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor;
+import com.ruoyi.framework.interceptor.AnonymousInterceptor;
 
 /**
  * 閫氱敤閰嶇疆
@@ -25,6 +26,9 @@
 {
     @Autowired
     private RepeatSubmitInterceptor repeatSubmitInterceptor;
+
+    @Autowired
+    private AnonymousInterceptor anonymousInterceptor;
 
     @Override
     public void addResourceHandlers(ResourceHandlerRegistry registry)
@@ -40,12 +44,16 @@
     }
 
     /**
-     * 鑷畾涔夋嫤鎴鍒�
+     * 娣诲姞鎷︽埅鍣�
      */
     @Override
     public void addInterceptors(InterceptorRegistry registry)
     {
+        // 閲嶅鎻愪氦鎷︽埅鍣�
         registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
+        
+        // 鍖垮悕璁块棶鎷︽埅鍣�
+        registry.addInterceptor(anonymousInterceptor).addPathPatterns("/**");
     }
 
     /**
@@ -55,18 +63,12 @@
     public CorsFilter corsFilter()
     {
         CorsConfiguration config = new CorsConfiguration();
-        // 璁剧疆璁块棶婧愬湴鍧�
         config.addAllowedOriginPattern("*");
-        // 璁剧疆璁块棶婧愯姹傚ご
         config.addAllowedHeader("*");
-        // 璁剧疆璁块棶婧愯姹傛柟娉�
         config.addAllowedMethod("*");
-        // 鏈夋晥鏈� 1800绉�
         config.setMaxAge(1800L);
-        // 娣诲姞鏄犲皠璺緞锛屾嫤鎴竴鍒囪姹�
         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
         source.registerCorsConfiguration("/**", config);
-        // 杩斿洖鏂扮殑CorsFilter
         return new CorsFilter(source);
     }
 }
\ No newline at end of file
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java
index 511842b..ed6678c 100644
--- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java
@@ -20,6 +20,14 @@
 import com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter;
 import com.ruoyi.framework.security.handle.AuthenticationEntryPointImpl;
 import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl;
+import com.ruoyi.common.annotation.Anonymous;
+import org.springframework.security.web.util.matcher.RequestMatcher;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * spring security閰嶇疆
@@ -66,6 +74,26 @@
     @Autowired
     private PermitAllUrlProperties permitAllUrl;
 
+    @Autowired
+    private RequestMappingHandlerMapping requestMappingHandlerMapping;
+
+    /**
+     * 鑾峰彇鎵�鏈夋爣娉ㄤ簡@Anonymous鐨刄RL
+     */
+    private Set<String> getAnonymousUrls() {
+        Set<String> urls = new HashSet<>();
+        Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods();
+        for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethods.entrySet()) {
+            HandlerMethod handlerMethod = entry.getValue();
+            Anonymous anonymous = handlerMethod.getMethodAnnotation(Anonymous.class);
+            if (anonymous != null) {
+                Set<String> patterns = entry.getKey().getPatternValues();
+                urls.addAll(patterns);
+            }
+        }
+        return urls;
+    }
+
     /**
      * 韬唤楠岃瘉瀹炵幇
      */
@@ -96,6 +124,9 @@
     @Bean
     protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception
     {
+        // 鑾峰彇鎵�鏈夋爣娉ㄤ簡@Anonymous鐨刄RL
+        Set<String> anonymousUrls = getAnonymousUrls();
+        
         return httpSecurity
             // CSRF绂佺敤锛屽洜涓轰笉浣跨敤session
             .csrf(csrf -> csrf.disable())
@@ -107,17 +138,18 @@
             .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
             // 鍩轰簬token锛屾墍浠ヤ笉闇�瑕乻ession
             .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
-            // 娉ㄨВ鏍囪鍏佽鍖垮悕璁块棶鐨剈rl
-            .authorizeHttpRequests((requests) -> {
-                permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll());
-                // 瀵逛簬鐧诲綍login 娉ㄥ唽register 楠岃瘉鐮乧aptchaImage 鍏佽鍖垮悕璁块棶
-                requests.antMatchers("/login", "/register", "/captchaImage").permitAll()
-                    // 闈欐�佽祫婧愶紝鍙尶鍚嶈闂�
-                    .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
-                    .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
-                    // 闄や笂闈㈠鐨勬墍鏈夎姹傚叏閮ㄩ渶瑕侀壌鏉冭璇�
-                    .anyRequest().authenticated();
-            })
+            // 杩囨护璇锋眰
+            .authorizeRequests()
+            // 瀵逛簬鐧诲綍login 娉ㄥ唽register 楠岃瘉鐮乧aptchaImage 鍏佽鍖垮悕璁块棶
+            .antMatchers("/login", "/register", "/captchaImage").permitAll()
+            // 娣诲姞鏍囨敞浜咢Anonymous鐨刄RL鍒板尶鍚嶈闂垪琛�
+            .antMatchers(anonymousUrls.toArray(new String[0])).permitAll()
+            // 闈欐�佽祫婧愶紝鍙尶鍚嶈闂�
+            .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
+            .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
+            // 闄や笂闈㈠鐨勬墍鏈夎姹傚叏閮ㄩ渶瑕侀壌鏉冭璇�
+            .anyRequest().authenticated()
+            .and()
             // 娣诲姞Logout filter
             .logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler))
             // 娣诲姞JWT filter
diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/AnonymousInterceptor.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/AnonymousInterceptor.java
new file mode 100644
index 0000000..1c070ef
--- /dev/null
+++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/AnonymousInterceptor.java
@@ -0,0 +1,68 @@
+package com.ruoyi.framework.interceptor;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.util.StringUtils;
+
+import com.ruoyi.common.annotation.Anonymous;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.system.service.ISysClientAppService;
+
+/**
+ * 鍖垮悕璁块棶鎷︽埅鍣�
+ */
+@Component
+public class AnonymousInterceptor implements HandlerInterceptor {
+
+    @Autowired
+    private ISysClientAppService clientAppService;
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+        // 濡傛灉涓嶆槸鏄犲皠鍒版柟娉曪紝鐩存帴閫氳繃
+        if (!(handler instanceof HandlerMethod)) {
+            return true;
+        }
+
+        // 鑾峰彇鏂规硶涓婄殑娉ㄨВ
+        HandlerMethod handlerMethod = (HandlerMethod) handler;
+        Anonymous anonymous = handlerMethod.getMethodAnnotation(Anonymous.class);
+        
+        // 濡傛灉鏂规硶涓婃病鏈夋敞瑙o紝鍒欒幏鍙栫被涓婄殑娉ㄨВ
+        if (anonymous == null) {
+            anonymous = handlerMethod.getBeanType().getAnnotation(Anonymous.class);
+        }
+
+        // 濡傛灉娌℃湁娉ㄨВ锛岀洿鎺ラ�氳繃
+        if (anonymous == null) {
+            return true;
+        }
+
+        // 鑾峰彇璇锋眰鍙傛暟
+        String appId = request.getParameter("appId");
+        String sign = request.getParameter("sign");
+        String timestamp = request.getParameter("timestamp");
+        if(anonymous.needSign()){
+            if(appId == null || sign == null || timestamp == null){
+                throw new ServiceException("缂哄皯蹇呰鍙傛暟");
+
+            }
+        }
+        // 楠岃瘉蹇呰鍙傛暟
+        if (StringUtils.hasText(appId) && StringUtils.hasText(sign) && StringUtils.hasText(timestamp)) {
+            // 楠岃瘉绛惧悕
+            if (clientAppService.validateSign(appId, sign, timestamp)) {
+                return true;
+            }
+            throw new ServiceException("绛惧悕楠岃瘉澶辫触");
+        }
+
+        // 濡傛灉娌℃湁楠岃瘉鍙傛暟锛屼篃鍏佽閫氳繃锛堥�傜敤浜庝笉闇�瑕侀獙璇佺殑鍖垮悕鎺ュ彛锛�
+        return true;
+    }
+} 
\ No newline at end of file
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysClientApp.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysClientApp.java
index 6c1de16..f06d5a6 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysClientApp.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SysClientApp.java
@@ -4,6 +4,7 @@
 import com.ruoyi.common.core.domain.BaseEntity;
 import org.apache.commons.lang3.builder.ToStringBuilder;
 import org.apache.commons.lang3.builder.ToStringStyle;
+import com.fasterxml.jackson.annotation.JsonFormat;
 
 import java.util.Date;
 
@@ -30,10 +31,12 @@
 
     /** 鏈夋晥鏈熷紑濮嬫椂闂� */
     @Excel(name = "鏈夋晥鏈熷紑濮嬫椂闂�", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date validStartTime;
 
     /** 鏈夋晥鏈熺粨鏉熸椂闂� */
     @Excel(name = "鏈夋晥鏈熺粨鏉熸椂闂�", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date validEndTime;
 
     /** 鐘舵�侊紙0姝e父 1鍋滅敤锛� */
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysClientAppMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysClientAppMapper.java
index d9f364a..6589787 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysClientAppMapper.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysClientAppMapper.java
@@ -54,4 +54,12 @@
      * @return 缁撴灉
      */
     public int deleteSysClientAppByAppIds(Long[] appIds);
+
+    /**
+     * 鏍规嵁搴旂敤鏍囪瘑鏌ヨ搴旂敤淇℃伅
+     * 
+     * @param appKey 搴旂敤鏍囪瘑
+     * @return 搴旂敤淇℃伅
+     */
+    public SysClientApp selectSysClientAppByAppKey(String appKey);
 } 
\ No newline at end of file
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysClientAppService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysClientAppService.java
index 5d2b008..4bf12ce 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysClientAppService.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysClientAppService.java
@@ -54,4 +54,22 @@
      * @return 缁撴灉
      */
     public int deleteSysClientAppByAppId(Long appId);
+
+    /**
+     * 楠岃瘉绛惧悕
+     * 
+     * @param appId 搴旂敤ID
+     * @param sign 绛惧悕
+     * @param timestamp 鏃堕棿鎴�
+     * @return 楠岃瘉缁撴灉
+     */
+    public boolean validateSign(String appId, String sign, String timestamp);
+
+    /**
+     * 閫氳繃搴旂敤鏍囪瘑鏌ヨ瀹㈡埛搴旂敤閰嶇疆
+     * 
+     * @param appKey 搴旂敤鏍囪瘑
+     * @return 瀹㈡埛搴旂敤閰嶇疆
+     */
+    public SysClientApp selectSysClientAppByAppKey(String appKey);
 } 
\ No newline at end of file
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysClientAppServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysClientAppServiceImpl.java
index f7e1844..f36ecea 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysClientAppServiceImpl.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysClientAppServiceImpl.java
@@ -8,6 +8,8 @@
 import com.ruoyi.system.service.ISysClientAppService;
 import com.ruoyi.common.utils.SecurityUtils;
 
+import static com.ruoyi.common.utils.SecurityUtils.md5;
+
 /**
  * 瀹㈡埛搴旂敤閰嶇疆 鏈嶅姟灞傚疄鐜�
  */
@@ -25,6 +27,18 @@
     @Override
     public SysClientApp selectSysClientAppByAppId(Long appId) {
         return sysClientAppMapper.selectSysClientAppByAppId(appId);
+    }
+
+    /**
+     * 閫氳繃搴旂敤鏍囪瘑鏌ヨ瀹㈡埛搴旂敤閰嶇疆
+     * 
+     * @param appKey 搴旂敤鏍囪瘑
+     * @return 瀹㈡埛搴旂敤閰嶇疆
+     */
+    @Override
+    public SysClientApp selectSysClientAppByAppKey(String appKey)
+    {
+        return sysClientAppMapper.selectSysClientAppByAppKey(appKey);
     }
 
     /**
@@ -118,4 +132,42 @@
     public int deleteSysClientAppByAppId(Long appId) {
         return sysClientAppMapper.deleteSysClientAppByAppId(appId);
     }
+
+    @Override
+    public boolean validateSign(String appId, String sign, String timestamp) {
+        // 鏍规嵁appId鑾峰彇搴旂敤淇℃伅
+        SysClientApp clientApp = sysClientAppMapper.selectSysClientAppByAppKey(appId);
+        if (clientApp == null) {
+            return false;
+        }
+
+        // 楠岃瘉搴旂敤鏄惁鏈夋晥
+        if (!"0".equals(clientApp.getStatus())) {
+            return false;
+        }
+
+        // 楠岃瘉鏈夋晥鏈�
+        if (clientApp.getValidStartTime() != null && clientApp.getValidEndTime() != null) {
+            long currentTime = System.currentTimeMillis();
+            if (currentTime < clientApp.getValidStartTime().getTime() 
+                || currentTime > clientApp.getValidEndTime().getTime()) {
+                return false;
+            }
+        }
+
+        // 鐢熸垚绛惧悕
+        String serverSign = generateSign(appId, clientApp.getSecurityKey(), timestamp);
+        
+        // 姣旇緝绛惧悕
+        return sign.equals(serverSign);
+    }
+
+    /**
+     * 鐢熸垚绛惧悕
+     * 绛惧悕瑙勫垯锛歁D5(appId + timestamp + securityKey)
+     */
+    private String generateSign(String appId, String securityKey, String timestamp) {
+        String signStr = appId + timestamp + securityKey;
+        return md5(signStr);
+    }
 } 
\ No newline at end of file
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysClientAppMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysClientAppMapper.xml
index 2e3eca6..951c4eb 100644
--- a/ruoyi-system/src/main/resources/mapper/system/SysClientAppMapper.xml
+++ b/ruoyi-system/src/main/resources/mapper/system/SysClientAppMapper.xml
@@ -40,6 +40,11 @@
         where app_id = #{appId}
     </select>
         
+    <select id="selectSysClientAppByAppKey" parameterType="String" resultMap="SysClientAppResult">
+        <include refid="selectSysClientAppVo"/>
+        where app_key = #{appKey} and del_flag = '0'
+    </select>
+        
     <insert id="insertSysClientApp" parameterType="SysClientApp" useGeneratedKeys="true" keyProperty="appId">
         insert into sys_client_app
         <trim prefix="(" suffix=")" suffixOverrides=",">
diff --git a/ruoyi-ui/src/views/system/clientApp/index.vue b/ruoyi-ui/src/views/system/clientApp/index.vue
index 31ef400..fbb651e 100644
--- a/ruoyi-ui/src/views/system/clientApp/index.vue
+++ b/ruoyi-ui/src/views/system/clientApp/index.vue
@@ -309,6 +309,14 @@
     submitForm() {
       this.$refs["form"].validate(valid => {
         if (valid) {
+          // 澶勭悊鏃ユ湡鏍煎紡
+          if (this.form.validStartTime) {
+            this.form.validStartTime = this.parseTime(this.form.validStartTime, '{y}-{m}-{d} {h}:{i}:{s}');
+          }
+          if (this.form.validEndTime) {
+            this.form.validEndTime = this.parseTime(this.form.validEndTime, '{y}-{m}-{d} {h}:{i}:{s}');
+          }
+          
           if (this.form.appId != null) {
             updateClientApp(this.form).then(response => {
               this.$modal.msgSuccess("淇敼鎴愬姛");
diff --git a/sql/sql_client_app_menu.sql b/sql/sql_client_app_menu.sql
new file mode 100644
index 0000000..8733e31
--- /dev/null
+++ b/sql/sql_client_app_menu.sql
@@ -0,0 +1,22 @@
+_-- 鑿滃崟 SQL
+INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, STATUS, perms, icon, create_by, create_time, update_by, update_time, remark)
+VALUES('瀹㈡埛搴旂敤閰嶇疆', '1', '1', 'clientApp', 'system/clientApp/index', 1, 0, 'C', '0', '0', 'system:clientApp:list', 'app', 'admin', SYSDATE(), '', NULL, '瀹㈡埛搴旂敤閰嶇疆鑿滃崟');
+
+-- 鎸夐挳鐖惰彍鍗旾D
+SELECT @parentId := LAST_INSERT_ID();
+
+-- 鎸夐挳 SQL
+INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, STATUS, perms, icon, create_by, create_time, update_by, update_time, remark)
+VALUES('瀹㈡埛搴旂敤閰嶇疆鏌ヨ', @parentId, '1',  '#', '', 1, 0, 'F', '0', '0', 'system:clientApp:query',        '#', 'admin', SYSDATE(), '', NULL, '');
+
+INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, STATUS, perms, icon, create_by, create_time, update_by, update_time, remark)
+VALUES('瀹㈡埛搴旂敤閰嶇疆鏂板', @parentId, '2',  '#', '', 1, 0, 'F', '0', '0', 'system:clientApp:add',          '#', 'admin', SYSDATE(), '', NULL, '');
+
+INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, STATUS, perms, icon, create_by, create_time, update_by, update_time, remark)
+VALUES('瀹㈡埛搴旂敤閰嶇疆淇敼', @parentId, '3',  '#', '', 1, 0, 'F', '0', '0', 'system:clientApp:edit',         '#', 'admin', SYSDATE(), '', NULL, '');
+
+INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, STATUS, perms, icon, create_by, create_time, update_by, update_time, remark)
+VALUES('瀹㈡埛搴旂敤閰嶇疆鍒犻櫎', @parentId, '4',  '#', '', 1, 0, 'F', '0', '0', 'system:clientApp:remove',       '#', 'admin', SYSDATE(), '', NULL, '');
+
+INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, STATUS, perms, icon, create_by, create_time, update_by, update_time, remark)
+VALUES('瀹㈡埛搴旂敤閰嶇疆瀵煎嚭', @parentId, '5',  '#', '', 1, 0, 'F', '0', '0', 'system:clientApp:export',       '#', 'admin', SYSDATE(), '', NULL, '');
\ No newline at end of file

--
Gitblit v1.9.1