Ver Fonte

Merge branch 'dev' of http://192.168.1.245:11111/jinjilong/onlineEducation-fwd into dev

honorfire há 5 meses atrás
pai
commit
2aec896a5e
12 ficheiros alterados com 1297 adições e 32 exclusões
  1. 8 0
      snowy-plugin/snowy-plugin-auth/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/core/pojo/SaBaseLoginUser.java
  2. 25 21
      snowy-plugin/snowy-plugin-auth/snowy-plugin-auth-func/src/main/java/vip/xiaonuo/auth/modular/login/service/impl/AuthServiceImpl.java
  3. 0 2
      snowy-plugin/snowy-plugin-dev/snowy-plugin-dev-func/pom.xml
  4. 2 1
      snowy-plugin/snowy-plugin-dev/snowy-plugin-dev-func/src/main/java/vip/xiaonuo/dev/modular/job/enums/DevJobCategoryEnum.java
  5. 77 0
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/job/task/PwdJobTimerTaskRunner.java
  6. 58 0
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/service/impl/CollegeUserServiceImpl.java
  7. 175 0
      snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/util/DateTimeUtil.java
  8. 4 0
      snowy-plugin/snowy-plugin-sys/snowy-plugin-sys-func/pom.xml
  9. 10 0
      snowy-plugin/snowy-plugin-sys/snowy-plugin-sys-func/src/main/java/vip/xiaonuo/sys/modular/user/entity/SysUser.java
  10. 73 8
      snowy-plugin/snowy-plugin-sys/snowy-plugin-sys-func/src/main/java/vip/xiaonuo/sys/modular/user/service/impl/SysUserServiceImpl.java
  11. 690 0
      snowy-plugin/snowy-plugin-sys/snowy-plugin-sys-func/src/main/java/vip/xiaonuo/sys/modular/user/util/AppUseAggregationUtil.java
  12. 175 0
      snowy-plugin/snowy-plugin-sys/snowy-plugin-sys-func/src/main/java/vip/xiaonuo/sys/modular/user/util/DateTimeUtil.java

+ 8 - 0
snowy-plugin/snowy-plugin-auth/snowy-plugin-auth-api/src/main/java/vip/xiaonuo/auth/core/pojo/SaBaseLoginUser.java

@@ -12,6 +12,7 @@
  */
 package vip.xiaonuo.auth.core.pojo;
 
+import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Getter;
@@ -279,6 +280,13 @@ public abstract class SaBaseLoginUser {
     @ApiModelProperty(value = "学届", position = 7)
     private String fallDue;
 
+    /** 密码是否过期(0 不过期  1过期) */
+    private String expired;
+
+    /** 密码过期时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date expiredTime;
+
     /** 是否可登录,由继承类实现 */
     public abstract Boolean getEnabled();
 

+ 25 - 21
snowy-plugin/snowy-plugin-auth/snowy-plugin-auth-func/src/main/java/vip/xiaonuo/auth/modular/login/service/impl/AuthServiceImpl.java

@@ -251,6 +251,7 @@ public class AuthServiceImpl implements AuthService {
     public String doLogin(AuthAccountPasswordLoginParam authAccountPasswordLoginParam, String type) {
         // 判断账号是否被封禁
         isDisableTime(authAccountPasswordLoginParam.getAccount());
+
         // 获取账号
         String account = authAccountPasswordLoginParam.getAccount();
         // 获取密码
@@ -263,26 +264,27 @@ public class AuthServiceImpl implements AuthService {
         } else {
             AuthDeviceTypeEnum.validate(device);
         }
+
         // 校验验证码
-        String defaultCaptchaOpen = devConfigApi.getValueByKey(SNOWY_SYS_DEFAULT_CAPTCHA_OPEN_KEY);
-        if(ObjectUtil.isNotEmpty(defaultCaptchaOpen)) {
-            if(Convert.toBool(defaultCaptchaOpen)) {
-                // 获取验证码
-                String validCode = authAccountPasswordLoginParam.getValidCode();
-                // 获取验证码请求号
-                String validCodeReqNo = authAccountPasswordLoginParam.getValidCodeReqNo();
-                // 开启验证码则必须传入验证码
-                if(ObjectUtil.isEmpty(validCode)) {
-                    throw new CommonException(AuthExceptionEnum.VALID_CODE_EMPTY.getValue());
-                }
-                // 开启验证码则必须传入验证码请求号
-                if(ObjectUtil.isEmpty(validCodeReqNo)) {
-                    throw new CommonException(AuthExceptionEnum.VALID_CODE_REQ_NO_EMPTY.getValue());
-                }
-                // 执行校验验证码
-                validValidCode(null, validCode, validCodeReqNo);
-            }
-        }
+//        String defaultCaptchaOpen = devConfigApi.getValueByKey(SNOWY_SYS_DEFAULT_CAPTCHA_OPEN_KEY);
+//        if(ObjectUtil.isNotEmpty(defaultCaptchaOpen)) {
+//            if(Convert.toBool(defaultCaptchaOpen)) {
+//                // 获取验证码
+//                String validCode = authAccountPasswordLoginParam.getValidCode();
+//                // 获取验证码请求号
+//                String validCodeReqNo = authAccountPasswordLoginParam.getValidCodeReqNo();
+//                // 开启验证码则必须传入验证码
+//                if(ObjectUtil.isEmpty(validCode)) {
+//                    throw new CommonException(AuthExceptionEnum.VALID_CODE_EMPTY.getValue());
+//                }
+//                // 开启验证码则必须传入验证码请求号
+//                if(ObjectUtil.isEmpty(validCodeReqNo)) {
+//                    throw new CommonException(AuthExceptionEnum.VALID_CODE_REQ_NO_EMPTY.getValue());
+//                }
+//                // 执行校验验证码
+//                validValidCode(null, validCode, validCodeReqNo);
+//            }
+//        }
         // SM2解密并获得前端传来的密码哈希值
         String passwordHash;
         try {
@@ -383,17 +385,19 @@ public class AuthServiceImpl implements AuthService {
         Integer number = (Integer) commonCacheOperator.get(loginErrorKey);
         if (number == null) {
             // 如果redis中没有保存,代表失败第一次
-            number = 2;
+            number = 1;
             commonCacheOperator.put(loginErrorKey, number,5 * 60);
             return;
         }
-        if (number < 5) {
+        if (number < 2) {  //第一遍   redis 存1   第二遍   redis   存2    第三遍   redis  存3 封禁账号  提示密码错误
             number++;
             commonCacheOperator.put(loginErrorKey, number,5 * 60);
             return;
         }
         // 第五次封禁账号,第六次进入isDisableTime方法,返回用户还需等待时间
         StpUtil.disable(userAccount, 5 * 60);
+        //解封
+        //StpUtil.untieDisable(userAccount);
         // 删除redis 中的key
         clearLoginErrorTimes(userAccount);
 

+ 0 - 2
snowy-plugin/snowy-plugin-dev/snowy-plugin-dev-func/pom.xml

@@ -44,8 +44,6 @@
             <artifactId>snowy-plugin-ten-api</artifactId>
         </dependency>
 
-
-
         <!-- logback-classic -->
         <dependency>
             <groupId>ch.qos.logback</groupId>

+ 2 - 1
snowy-plugin/snowy-plugin-dev/snowy-plugin-dev-func/src/main/java/vip/xiaonuo/dev/modular/job/enums/DevJobCategoryEnum.java

@@ -34,6 +34,7 @@ public enum DevJobCategoryEnum {
      */
     BIZ("BIZ"),
 
+    SYS("SYS"),
     /**
      * 考试任务-开始
      */
@@ -52,7 +53,7 @@ public enum DevJobCategoryEnum {
     }
 
     public static void validate(String value) {
-        boolean flag = FRM.getValue().equals(value) || BIZ.getValue().equals(value) || EXAM_START.getValue().equals(value) || EXAM_END.getValue().equals(value);
+        boolean flag = FRM.getValue().equals(value) || BIZ.getValue().equals(value) || EXAM_START.getValue().equals(value) || EXAM_END.getValue().equals(value)|| SYS.getValue().equals(value);
         if(!flag) {
             throw new CommonException("不支持的定时任务分类:{}", value);
         }

+ 77 - 0
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/job/task/PwdJobTimerTaskRunner.java

@@ -0,0 +1,77 @@
+package vip.xiaonuo.disk.job.task;
+
+import com.alibaba.fastjson.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import vip.xiaonuo.common.timer.CommonTimerTaskRunner;
+import vip.xiaonuo.dev.api.DevJobApi;
+import vip.xiaonuo.dev.modular.job.param.DevJobIdParam;
+import vip.xiaonuo.dev.modular.job.service.DevJobService;
+import vip.xiaonuo.dev.modular.message.enums.DevMessageCategoryEnum;
+import vip.xiaonuo.dev.modular.message.param.DevMessageSendParam;
+import vip.xiaonuo.dev.modular.message.service.DevMessageService;
+import vip.xiaonuo.sys.modular.user.entity.SysUser;
+import vip.xiaonuo.sys.modular.user.mapper.SysUserMapper;
+
+import javax.annotation.Resource;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 密码到期定时任务类
+ */
+@Slf4j
+@Component
+public class PwdJobTimerTaskRunner implements CommonTimerTaskRunner {
+
+    @Resource
+    private DevMessageService devMessageService;
+
+    @Resource
+    private SysUserMapper sysUserMapper;
+
+    @Resource
+    private DevJobApi devJobApi;
+
+    @Resource
+    private DevJobService devJobService;
+    /**
+     * 任务执行的具体内容
+     *
+     * @author xuyuxiang
+     * @date 2022/8/15 16:09
+     **/
+    @Override
+    public void action(String jobId) {
+        log.info("密码到期定时任务类PwdJobTimerTaskRunner jobId:{} RUN...",jobId);
+        try {
+
+            if(jobId != null && !jobId.isEmpty()){
+
+                JSONObject jb = devJobApi.queryEntity(jobId);
+                if(jb != null && jb.getIntValue("code") == 200){
+                    JSONObject data = jb.getJSONObject("data");
+                    JSONObject extJson = JSONObject.parseObject(data.getString("extJson"));
+                    log.info("PwdJobTimerTaskRunner jobId:{}  extJson:{}",jobId,extJson);
+                    //根据用户id,修改用户密码状态
+                    SysUser user=new SysUser();
+                    user.setId(extJson.getString("userId"));
+                    user.setExpired("1");
+                    sysUserMapper.updateById(user);
+
+                }else{
+                    log.error("密码到期定时任务类PwdJobTimerTaskRunner jobId:{} Query Error", jobId);
+                }
+            }else{
+                log.error("密码到期定时任务类PwdJobTimerTaskRunner jobId:{} Query Error", jobId);
+            }
+            //运行完将定时任务进行关闭
+            DevJobIdParam devJobIdParam = new DevJobIdParam();
+            devJobIdParam.setId(jobId);
+            devJobService.stopJob(devJobIdParam);
+        } catch (Exception e) {
+            log.error("密码到期定时任务类PwdJobTimerTaskRunner  Error:{}", e);
+            e.printStackTrace();
+        }
+    }
+}

+ 58 - 0
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/service/impl/CollegeUserServiceImpl.java

@@ -18,9 +18,11 @@ import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollStreamUtil;
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.convert.Convert;
+import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.util.IdUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
@@ -43,6 +45,10 @@ import vip.xiaonuo.common.exception.CommonException;
 import vip.xiaonuo.common.page.CommonPageRequest;
 import vip.xiaonuo.common.pojo.CommonResult;
 import vip.xiaonuo.common.util.CommonCryptogramUtil;
+import vip.xiaonuo.dev.api.DevJobApi;
+import vip.xiaonuo.dev.modular.job.param.DevJobIdParam;
+import vip.xiaonuo.dev.modular.job.service.DevJobService;
+import vip.xiaonuo.dev.modular.message.enums.DevMessageCategoryEnum;
 import vip.xiaonuo.disk.domain.CollegeUser;
 import vip.xiaonuo.disk.domain.Dept;
 import vip.xiaonuo.disk.mapper.CollegeUserMapper;
@@ -51,6 +57,8 @@ import vip.xiaonuo.disk.param.collegeUser.CollegeUserEditParam;
 import vip.xiaonuo.disk.param.collegeUser.CollegeUserIdParam;
 import vip.xiaonuo.disk.param.collegeUser.CollegeUserPageParam;
 import vip.xiaonuo.disk.service.CollegeUserService;
+import vip.xiaonuo.disk.util.AppUseAggregationUtil;
+import vip.xiaonuo.disk.util.DateTimeUtil;
 import vip.xiaonuo.disk.util.HttpRequest;
 import vip.xiaonuo.sys.modular.org.mapper.SysOrgMapper;
 import vip.xiaonuo.sys.modular.user.entity.SysUser;
@@ -103,6 +111,13 @@ public class CollegeUserServiceImpl extends ServiceImpl<CollegeUserMapper, Colle
 
     @Resource(name = "loginUserApi")
     private SaBaseLoginUserApi loginUserApi;
+
+    @Resource
+    private DevJobApi devJobApi;
+
+    @Resource
+    private DevJobService devJobService;
+
     @Override
     public Page<CollegeUser> page(CollegeUserPageParam collegeUserPageParam) {
         QueryWrapper<CollegeUser> queryWrapper = new QueryWrapper<>();
@@ -193,6 +208,11 @@ public class CollegeUserServiceImpl extends ServiceImpl<CollegeUserMapper, Colle
                 user.setEmail(collegeUser.getEmail());
                 user.setPhone(collegeUser.getMobile());
                 user.setPassword(CommonCryptogramUtil.doHashValue("1Qaz@wsx"));
+                user.setExpired("0");
+                String nowTime=DateUtil.format(new Date(),"yyyy-MM-dd");
+                String expiredTime = AppUseAggregationUtil.offsetDay(nowTime, +90, "yyyy-MM-dd");
+                //过期时间
+                user.setExpiredTime(DateUtil.parse(expiredTime,"yyyy-MM-dd"));
                 List<String> deptIds = collegeUserAddParam.getPost().stream().map(Dept::getDeptId).collect(Collectors.toList());
                 if(CollectionUtil.isNotEmpty(deptIds)){
                     List<String> orgNameList=sysOrgMapper.selectOrgNameList(deptIds);
@@ -217,6 +237,8 @@ public class CollegeUserServiceImpl extends ServiceImpl<CollegeUserMapper, Colle
                 }
                 user.setCreateTime(new Date());
                 sysUserMapper.insert(user);
+                //添加密码到期提醒
+                this.addJob(user);
             }
             log.info("用户数据下发成功:{}", JSON.toJSONString(collegeUserAddParam));
             log.info("用户数据下发成功,下发条数:{}",count);
@@ -381,4 +403,40 @@ public class CollegeUserServiceImpl extends ServiceImpl<CollegeUserMapper, Colle
         commonCacheOperator.remove(loginErrorKey);
     }
 
+
+    public void addJob(SysUser user){
+
+        JSONObject jSONObject = new JSONObject();
+        jSONObject.put("userId", user.getId());
+        jSONObject.put("name", user.getName());
+
+        JSONObject jobParam = new JSONObject();
+        jobParam.put("name", user.getId()+"-"+user.getAccount()+"-"+user.getName());
+        jobParam.put("category", DevMessageCategoryEnum.SYS.getValue());
+        jobParam.put("actionClass", "vip.xiaonuo.disk.job.task.PwdJobTimerTaskRunner");
+        jobParam.put("sortCode", 99);
+        jobParam.put("extJson", jSONObject);
+        String cronExpression = DateTimeUtil.generateCronExpression(user.getExpiredTime());
+        jobParam.put("cronExpression", cronExpression);
+        JSONObject addJobResult = devJobApi.addJob(jobParam);
+
+        if(addJobResult != null && addJobResult.getInteger("code") != null && addJobResult.getIntValue("code") == 200){
+            DevJobIdParam devJobIdParam = new DevJobIdParam();
+            devJobIdParam.setId(addJobResult.getJSONObject("data").getString("id"));
+            devJobService.runJob(devJobIdParam);
+            //将任务id存到用户表
+            SysUser sysUser = new SysUser();
+            sysUser.setId(user.getId());
+            sysUser.setJobId(addJobResult.getJSONObject("data").getString("id"));
+            sysUserMapper.updateById(sysUser);
+        }else{
+            String msg = "";
+            if(addJobResult != null && addJobResult.getString("msg") != null){
+                msg = addJobResult.getString("msg");
+            }
+            log.error("任务添加失败,{}",msg);
+            throw new CommonException("任务添加失败");
+        }
+    }
+
 }

+ 175 - 0
snowy-plugin/snowy-plugin-disk/snowy-plugin-disk-func/src/main/java/vip/xiaonuo/disk/util/DateTimeUtil.java

@@ -0,0 +1,175 @@
+package vip.xiaonuo.disk.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.Duration;
+import java.util.*;
+
+/**
+ * @version 3.5.0
+ * @description: The type Date time util.
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/12/25 9:45
+ */
+public class DateTimeUtil {
+
+    private static final Logger logger = LoggerFactory.getLogger(DateTimeUtil.class);
+    /**
+     * The constant STANDER_FORMAT.
+     */
+    public static final String STANDER_FORMAT = "yyyy-MM-dd HH:mm:ss";
+    /**
+     * The constant STANDER_SHORT_FORMAT.
+     */
+    public static final String STANDER_SHORT_FORMAT = "yyyy-MM-dd";
+
+    /**
+     * Add duration date.
+     *
+     * @param date     the date
+     * @param duration the duration
+     * @return the date
+     */
+    public static Date addDuration(Date date, Duration duration) {
+        Calendar ca = Calendar.getInstance();
+        ca.setTime(date);
+        ca.add(Calendar.SECOND, (int) duration.getSeconds());
+        return ca.getTime();
+    }
+
+    /**
+     * Date format string.
+     *
+     * @param date the date
+     * @return the string
+     */
+    public static String dateFormat(Date date) {
+        if (null == date) {
+            return "";
+        }
+        DateFormat dateFormat = new SimpleDateFormat(STANDER_FORMAT);
+        return dateFormat.format(date);
+    }
+
+    /**
+     * Date short format string.
+     *
+     * @param date the date
+     * @return the string
+     */
+    public static String dateShortFormat(Date date) {
+        if (null == date) {
+            return "";
+        }
+        DateFormat dateFormat = new SimpleDateFormat(STANDER_SHORT_FORMAT);
+        return dateFormat.format(date);
+    }
+
+    /**
+     * Parse date.
+     *
+     * @param dateStr the date str
+     * @param format  the format
+     * @return the date
+     */
+    public static Date parse(String dateStr, String format) {
+        try {
+            return new SimpleDateFormat(format).parse(dateStr);
+        } catch (ParseException e) {
+            logger.error(e.getMessage(), e);
+        }
+        return null;
+    }
+
+    /**
+     * Gets month start day.
+     *
+     * @return the month start day
+     */
+    public static Date getMonthStartDay() {
+        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd 00:00:00");
+        Calendar cale = Calendar.getInstance();
+        cale.add(Calendar.MONTH, 0);
+        cale.set(Calendar.DAY_OF_MONTH, 1);
+        String dateStr = formatter.format(cale.getTime());
+        return parse(dateStr, "yyyy-MM-dd HH:mm:ss");
+    }
+
+    /**
+     * Gets month end day.
+     *
+     * @return the month end day
+     */
+    public static Date getMonthEndDay() {
+        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd 23:59:59");
+        Calendar cale = Calendar.getInstance();
+        cale.add(Calendar.MONTH, 1);
+        cale.set(Calendar.DAY_OF_MONTH, 0);
+        String dateStr = formatter.format(cale.getTime());
+        return parse(dateStr, STANDER_FORMAT);
+    }
+
+
+    /**
+     * Moth start to now format list.
+     *
+     * @return the list
+     */
+    public static List<String> MothStartToNowFormat() {
+        Date startTime = getMonthStartDay();
+        Calendar nowCalendar = Calendar.getInstance();
+        nowCalendar.setTime(new Date());
+        int mothDayCount = nowCalendar.get(Calendar.DAY_OF_MONTH);
+        List<String> mothDays = new ArrayList<>(mothDayCount);
+        Calendar startCalendar = new GregorianCalendar();
+        startCalendar.setTime(startTime);
+        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
+        mothDays.add(formatter.format(startTime));
+        for (int i = 0; i < mothDayCount - 1; i++) {
+            startCalendar.add(Calendar.DATE, 1);
+            Date end_date = startCalendar.getTime();
+            mothDays.add(formatter.format(end_date));
+        }
+        return mothDays;
+    }
+
+
+    /**
+     * Moth day list.
+     *
+     * @return the list
+     */
+    public static List<String> MothDay() {
+        Calendar endCalendar = Calendar.getInstance();
+        endCalendar.setTime(getMonthEndDay());
+        int endMothDay = endCalendar.get(Calendar.DAY_OF_MONTH);
+        List<String> list = new ArrayList<>(endMothDay);
+        for (int i = 1; i <= endMothDay; i++) {
+            list.add(String.valueOf(i));
+        }
+        return list;
+    }
+
+    /**
+     * 根据考试结束时间生成cron表达式
+     * @param endTime 考试结束时间
+     * @return cron表达式
+     */
+    public static String generateCronExpression(Date endTime) {
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(endTime);
+
+        int minute = calendar.get(Calendar.MINUTE);
+        int hour = calendar.get(Calendar.HOUR_OF_DAY);
+        int day = calendar.get(Calendar.DAY_OF_MONTH);
+        int month = calendar.get(Calendar.MONTH) + 1; // Calendar.MONTH是从0开始的
+        int year = calendar.get(Calendar.YEAR);
+
+        // 生成类似 "0 minute hour day month ? year" 的cron表达式
+        return String.format("0 %d %d %d %d ? %d", minute, hour, day, month, year);
+    }
+}

+ 4 - 0
snowy-plugin/snowy-plugin-sys/snowy-plugin-sys-func/pom.xml

@@ -42,5 +42,9 @@
             <groupId>vip.xiaonuo</groupId>
             <artifactId>snowy-plugin-disk-api</artifactId>
         </dependency>
+        <dependency>
+            <groupId>vip.xiaonuo</groupId>
+            <artifactId>snowy-plugin-dev-func</artifactId>
+        </dependency>
     </dependencies>
 </project>

+ 10 - 0
snowy-plugin/snowy-plugin-sys/snowy-plugin-sys-func/src/main/java/vip/xiaonuo/sys/modular/user/entity/SysUser.java

@@ -16,6 +16,7 @@ import com.baomidou.mybatisplus.annotation.FieldStrategy;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fhs.core.trans.anno.Trans;
 import com.fhs.core.trans.constant.TransType;
@@ -350,4 +351,13 @@ public class SysUser extends CommonEntity {
     /** 账号类型 */
     private String userCode;
 
+    /** 密码是否过期 */
+    private String expired;
+
+    /** 过期时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date expiredTime;
+
+    /** 任务id */
+    private String jobId;
 }

+ 73 - 8
snowy-plugin/snowy-plugin-sys/snowy-plugin-sys-func/src/main/java/vip/xiaonuo/sys/modular/user/service/impl/SysUserServiceImpl.java

@@ -56,6 +56,7 @@ import com.baomidou.mybatisplus.core.toolkit.IdWorker;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fhs.trans.service.impl.TransService;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.poi.ss.usermodel.*;
 import org.apache.poi.xwpf.usermodel.XWPFDocument;
 import org.springframework.stereotype.Service;
@@ -72,11 +73,12 @@ import vip.xiaonuo.common.excel.CommonExcelCustomMergeStrategy;
 import vip.xiaonuo.common.exception.CommonException;
 import vip.xiaonuo.common.listener.CommonDataChangeEventCenter;
 import vip.xiaonuo.common.page.CommonPageRequest;
+import vip.xiaonuo.common.pojo.CommonResult;
 import vip.xiaonuo.common.util.*;
-import vip.xiaonuo.dev.api.DevConfigApi;
-import vip.xiaonuo.dev.api.DevEmailApi;
-import vip.xiaonuo.dev.api.DevMessageApi;
-import vip.xiaonuo.dev.api.DevSmsApi;
+import vip.xiaonuo.dev.api.*;
+import vip.xiaonuo.dev.modular.job.param.DevJobIdParam;
+import vip.xiaonuo.dev.modular.job.service.DevJobService;
+import vip.xiaonuo.dev.modular.message.enums.DevMessageCategoryEnum;
 import vip.xiaonuo.mobile.vip.MobileButtonApi;
 import vip.xiaonuo.mobile.vip.MobileMenuApi;
 import vip.xiaonuo.sys.modular.org.entity.SysOrg;
@@ -102,6 +104,8 @@ import vip.xiaonuo.sys.modular.user.mapper.SysUserMapper;
 import vip.xiaonuo.sys.modular.user.param.*;
 import vip.xiaonuo.sys.modular.user.result.*;
 import vip.xiaonuo.sys.modular.user.service.SysUserService;
+import vip.xiaonuo.sys.modular.user.util.AppUseAggregationUtil;
+import vip.xiaonuo.sys.modular.user.util.DateTimeUtil;
 
 import javax.annotation.Resource;
 import javax.servlet.http.HttpServletResponse;
@@ -119,6 +123,7 @@ import java.util.stream.Collectors;
  * @author xuyuxiang
  * @date 2022/2/23 18:43
  **/
+@Slf4j
 @Service
 public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {
 
@@ -175,7 +180,10 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
 
     @Resource
     private SysUserMapper sysUserMapper;
-
+    @Resource
+    private DevJobService devJobService;
+    @Resource
+    private DevJobApi devJobApi;
     @Override
     public SysLoginUser getUserById(String id) {
         SysUser sysUser = this.getById(id);
@@ -768,9 +776,66 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
         if (!CommonCryptogramUtil.doHashValue(password).equals(sysUser.getPassword())) {
             throw new CommonException("原密码错误");
         }
-        this.update(new LambdaUpdateWrapper<SysUser>().eq(SysUser::getId,
-                sysUser.getId()).set(SysUser::getPassword,
-                CommonCryptogramUtil.doHashValue(newPassword)));
+
+        //根据修改密码的时间将到期时间推迟90天
+        String nowTime=DateUtil.format(new Date(),"yyyy-MM-dd");
+        String expiredTime = AppUseAggregationUtil.offsetDay(nowTime, +90, "yyyy-MM-dd");
+        sysUser.setExpiredTime(DateUtil.parse(expiredTime,"yyyy-MM-dd"));
+        this.update(new LambdaUpdateWrapper<SysUser>()
+                .eq(SysUser::getId,sysUser.getId())
+                .set(SysUser::getPassword, CommonCryptogramUtil.doHashValue(newPassword))
+                .set(SysUser::getExpiredTime, expiredTime)
+                .set(SysUser::getExpired, "0")
+                );
+        //添加密码到期提醒
+        this.deleteJob(sysUser);
+
+    }
+    public void deleteJob(SysUser user){
+        List<String> jobIds = new ArrayList<String>();
+        jobIds.add(user.getJobId());
+        com.alibaba.fastjson.JSONObject deleteJobResult = devJobApi.deleteJob(jobIds);
+        if(deleteJobResult != null && deleteJobResult.getInteger("code") == 200){
+            this.addJob(user);
+            log.error("任务添加成功");
+        }else{
+            log.error("任务删除失败,删除之前任务异常。{}",deleteJobResult);
+            throw new CommonException("任务删除失败,删除之前任务异常。");
+        }
+    }
+    public void addJob(SysUser user){
+
+        com.alibaba.fastjson.JSONObject jSONObject = new com.alibaba.fastjson.JSONObject();
+        jSONObject.put("userId", user.getId());
+        jSONObject.put("name", user.getName());
+
+        com.alibaba.fastjson.JSONObject jobParam = new com.alibaba.fastjson.JSONObject();
+        jobParam.put("name", user.getId()+"-"+user.getAccount()+"-"+user.getName());
+        jobParam.put("category", DevMessageCategoryEnum.SYS.getValue());
+        jobParam.put("actionClass", "vip.xiaonuo.disk.job.task.PwdJobTimerTaskRunner");
+        jobParam.put("sortCode", 99);
+        jobParam.put("extJson", jSONObject);
+        String cronExpression = DateTimeUtil.generateCronExpression(user.getExpiredTime());
+        jobParam.put("cronExpression", cronExpression);
+        com.alibaba.fastjson.JSONObject addJobResult = devJobApi.addJob(jobParam);
+
+        if(addJobResult != null && addJobResult.getInteger("code") != null && addJobResult.getIntValue("code") == 200){
+            DevJobIdParam devJobIdParam = new DevJobIdParam();
+            devJobIdParam.setId(addJobResult.getJSONObject("data").getString("id"));
+            devJobService.runJob(devJobIdParam);
+            //将任务id存到用户表
+            SysUser sysUser = new SysUser();
+            sysUser.setId(user.getId());
+            sysUser.setJobId(addJobResult.getJSONObject("data").getString("id"));
+            sysUserMapper.updateById(sysUser);
+        }else{
+            String msg = "";
+            if(addJobResult != null && addJobResult.getString("msg") != null){
+                msg = addJobResult.getString("msg");
+            }
+            log.error("任务添加失败,{}",msg);
+            throw new CommonException("任务添加失败");
+        }
     }
 
     @Override

+ 690 - 0
snowy-plugin/snowy-plugin-sys/snowy-plugin-sys-func/src/main/java/vip/xiaonuo/sys/modular/user/util/AppUseAggregationUtil.java

@@ -0,0 +1,690 @@
+package vip.xiaonuo.sys.modular.user.util;
+
+
+
+import cn.hutool.core.date.DateField;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.NumberUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.ReUtil;
+import cn.hutool.core.util.StrUtil;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.text.NumberFormat;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+public class AppUseAggregationUtil {
+
+    private static final BigDecimal hundred = new BigDecimal(100);
+
+    //判断是否是数字类型
+    private static final String regex = "-?\\d+(\\.\\d+)?";
+
+
+
+    //省分列表
+    public static List<String> getProvinceCodeList() {
+        return Arrays.asList("210", "211", "212", "213", "214", "215", "216", "217", "218", "219", "220", "221", "222", "223", "224",
+                "225", "226", "227", "228", "229", "230", "231", "232", "233", "234", "235", "236", "237", "238", "239", "240");
+    }
+
+    //仅地区列表,无本部
+    public static List<String> getCityCodeList() {
+        return Arrays.asList("21000", "21002", "21003", "21004",
+                "21006", "21007", "21008", "21009", "21010", "21011", "21012", "21013", "21014", "21015", "21016", "21017", "21301", "21302", "21303",
+                "21304", "21305", "21306", "21307", "21308", "21309", "21401", "21402", "21403", "21404", "21405", "21406", "21407", "21408", "21409",
+                "21410", "21411", "21412", "21501", "21502", "21503", "21504", "21505", "21506", "21507", "21508", "21509", "21510", "21511", "21512",
+                "21513", "21514", "21515", "21516", "21517", "21518", "21519", "21520", "21521", "21601", "21602", "21603", "21604", "21605", "21606", "21607",
+                "21608", "21609", "21610", "21611", "21612", "21613", "21614", "21701", "21702", "21703", "21704", "21705", "21708", "21709", "21801", "21802",
+                "21803", "21804", "21805", "21806", "21807", "21808", "21809", "21810", "21811", "21902", "21903", "21904", "21905", "21906", "21907", "21908",
+                "21909", "21910", "21911", "21912", "21913", "21914", "21915", "21916", "21917", "21918", "21919", "22007", "22008", "22009", "22101", "22102",
+                "22103", "22104", "22105", "22106", "22107", "22108", "22109", "22110", "22111", "22112", "22116", "22201", "22202", "22203", "22204", "22205",
+                "22206", "22207", "22208", "22209", "22210", "22211", "22212", "22301", "22302", "22303", "22304", "22305", "22306", "22307", "22308", "22309",
+                "22310", "22311", "22312", "22313", "22401", "22402", "22403", "22404", "22405", "22406", "22407", "22408", "22409", "22501", "22502", "22503",
+                "22504", "22505", "22506", "22507", "22508", "22509", "22510", "22511", "22512", "22513", "22601", "22602", "22603", "22604", "22605", "22606",
+                "22607", "22608", "22609", "22610", "22611", "22700", "22701", "22702", "22703", "22704", "22705", "22706", "22707", "22708", "22709", "22710",
+                "22711", "22712", "22713", "22800", "22801", "22802", "22803", "22804", "22805", "22807", "22808", "22809", "22810", "22811", "22812", "22901",
+                "22902", "22903", "22904", "22905", "23000", "23001", "23002", "23003", "23004", "23005", "23006", "23007", "23008", "23100", "23101", "23102",
+                "23103", "23104", "23105", "23107", "23108", "23109", "23110", "23112", "23113", "23114", "23115", "23116", "23117", "23201", "23202", "23203",
+                "23204", "23205", "23206", "23207", "23208", "23209", "23210", "23211", "23301", "23302", "23303", "23304", "23305", "23306", "23307", "23308",
+                "23309", "23310", "23501", "23502", "23503", "23504", "23505", "23506", "23507", "23508", "23509", "23510", "23511", "23512", "23513", "23514",
+                "23515", "23516", "23517", "23701", "23707", "23708", "23709", "23710", "23711", "23712", "23801", "23802", "23805", "23806", "23808", "23809",
+                "23810", "23811", "23812", "23813", "23814", "23815", "23816", "23901", "23902", "23903", "23906", "23910", "23911", "23913", "23914", "24001",
+                "24002", "24003", "24004", "24005", "24006", "24007", "24008", "24009", "24010", "24011", "22213", "22214", "22314", "23521", "23520", "23519",
+                "21812", "22019", "23912", "23916", "23915", "23909", "23908", "23904", "23905", "23907", "21414", "21413", "21707", "21706", "23803", "23804",
+                "23807","23518");
+    }
+
+    //自定义的本部+省分+地市
+    public static List<String> getAllRegionCodeList() {
+        return Arrays.asList("21099", "21199", "21299", "21399", "21499", "21599", "21699", "21799", "21899", "21999", "22099",
+                "22199", "22299", "22399", "22499", "22599", "22699", "22799", "22899", "22999", "23099", "23199", "23299", "23399", "23499", "23599", "23699", "23799", "23899", "23999", "24099",
+                "210", "211", "212", "213", "214", "215", "216", "217", "218", "219", "220", "221", "222", "223", "224", "225",
+                "226", "227", "228", "229", "230", "231", "232", "233", "234", "235", "236", "237", "238", "239", "240", "21000", "21002", "21003", "21004",
+                "21006", "21007", "21008", "21009", "21010", "21011", "21012", "21013", "21014", "21015", "21016", "21017", "21301", "21302", "21303",
+                "21304", "21305", "21306", "21307", "21308", "21309", "21401", "21402", "21403", "21404", "21405", "21406", "21407", "21408", "21409",
+                "21410", "21411", "21412", "21501", "21502", "21503", "21504", "21505", "21506", "21507", "21508", "21509", "21510", "21511", "21512",
+                "21513", "21514", "21515", "21516", "21517", "21518", "21519", "21520", "21521", "21601", "21602", "21603", "21604", "21605", "21606", "21607",
+                "21608", "21609", "21610", "21611", "21612", "21613", "21614", "21701", "21702", "21703", "21704", "21705", "21708", "21709", "21801", "21802",
+                "21803", "21804", "21805", "21806", "21807", "21808", "21809", "21810", "21811", "21902", "21903", "21904", "21905", "21906", "21907", "21908",
+                "21909", "21910", "21911", "21912", "21913", "21914", "21915", "21916", "21917", "21918", "21919", "22007", "22008", "22009", "22101", "22102",
+                "22103", "22104", "22105", "22106", "22107", "22108", "22109", "22110", "22111", "22112", "22116", "22201", "22202", "22203", "22204", "22205",
+                "22206", "22207", "22208", "22209", "22210", "22211", "22212", "22301", "22302", "22303", "22304", "22305", "22306", "22307", "22308", "22309",
+                "22310", "22311", "22312", "22313", "22401", "22402", "22403", "22404", "22405", "22406", "22407", "22408", "22409", "22501", "22502", "22503",
+                "22504", "22505", "22506", "22507", "22508", "22509", "22510", "22511", "22512", "22513", "22601", "22602", "22603", "22604", "22605", "22606",
+                "22607", "22608", "22609", "22610", "22611", "22700", "22701", "22702", "22703", "22704", "22705", "22706", "22707", "22708", "22709", "22710",
+                "22711", "22712", "22713", "22800", "22801", "22802", "22803", "22804", "22805", "22807", "22808", "22809", "22810", "22811", "22812", "22901",
+                "22902", "22903", "22904", "22905", "23000", "23001", "23002", "23003", "23004", "23005", "23006", "23007", "23008", "23100", "23101", "23102",
+                "23103", "23104", "23105", "23107", "23108", "23109", "23110", "23112", "23113", "23114", "23115", "23116", "23117", "23201", "23202", "23203",
+                "23204", "23205", "23206", "23207", "23208", "23209", "23210", "23211", "23301", "23302", "23303", "23304", "23305", "23306", "23307", "23308",
+                "23309", "23310", "23501", "23502", "23503", "23504", "23505", "23506", "23507", "23508", "23509", "23510", "23511", "23512", "23513", "23514",
+                "23515", "23516", "23517", "23701", "23707", "23708", "23709", "23710", "23711", "23712", "23801", "23802", "23805", "23806", "23808", "23809",
+                "23810", "23811", "23812", "23813", "23814", "23815", "23816", "23901", "23902", "23903", "23906", "23910", "23911", "23913", "23914", "24001",
+                "24002", "24003", "24004", "24005", "24006", "24007", "24008", "24009", "24010", "24011", "22213", "22214", "22314", "23521", "23520", "23519",
+                "21812", "22019", "23912", "23916", "23915", "23909", "23908", "23904", "23905", "23907", "21414", "21413", "21707", "21706", "23803", "23804",
+                "23807","23518");
+    }
+
+    //全部+自定义的本部+省分+地市  去除了直辖市本部
+    public static List<String> getAllRegionAndTotalCodeList() {
+        return Arrays.asList("all",
+                "210", "21099","21000", "21002", "21003", "21004", "21006", "21007", "21008", "21009", "21010", "21011", "21012", "21013", "21014", "21015", "21016", "21017",
+                "211", "212",
+                "213", "21399", "21301", "21302", "21303", "21304", "21305", "21306", "21307", "21308", "21309",
+                "214", "21499", "21401", "21402", "21403", "21404", "21405", "21406", "21407", "21408", "21409", "21410", "21411", "21412", "21413", "21414",
+                "215", "21599", "21501", "21502", "21503", "21504", "21505", "21506", "21507", "21508", "21509", "21510", "21511", "21512", "21513", "21514", "21515", "21516", "21517", "21518", "21519", "21520", "21521",
+                "216", "21699", "21601", "21602", "21603", "21604", "21605", "21606", "21607", "21608", "21609", "21610", "21611", "21612", "21613", "21614",
+                "217", "21799", "21701", "21702", "21703", "21704", "21705", "21706", "21707", "21708", "21709",
+                "218", "21899", "21801", "21802", "21803", "21804", "21805", "21806", "21807", "21808", "21809", "21810", "21811", "21812",
+                "219", "21999", "21902", "21903", "21904", "21905", "21906", "21907", "21908", "21909", "21910", "21911", "21912", "21913", "21914", "21915", "21916", "21917", "21918", "21919",
+                "220", "22099", "22007", "22008", "22009", "22019",
+                "221", "22199", "22101", "22102", "22103", "22104", "22105", "22106", "22107", "22108", "22109", "22110", "22111", "22112", "22116",
+                "222", "22299", "22201", "22202", "22203", "22204", "22205", "22206", "22207", "22208", "22209", "22210", "22211", "22212", "22213", "22214",
+                "223", "22399", "22301", "22302", "22303", "22304", "22305", "22306", "22307", "22308", "22309", "22310", "22311", "22312", "22313", "22314",
+                "224", "22499", "22401", "22402", "22403", "22404", "22405", "22406", "22407", "22408", "22409",
+                "225", "22599", "22501", "22502", "22503", "22504", "22505", "22506", "22507", "22508", "22509", "22510", "22511", "22512", "22513",
+                "226", "22699", "22601", "22602", "22603", "22604", "22605", "22606", "22607", "22608", "22609", "22610", "22611",
+                "227", "22799", "22700", "22701", "22702", "22703", "22704", "22705", "22706", "22707", "22708", "22709", "22710", "22711", "22712", "22713",
+                "228", "22899", "22800", "22801", "22802", "22803", "22804", "22805", "22807", "22808", "22809", "22810", "22811", "22812",
+                "229", "22999", "22901", "22902", "22903", "22904", "22905",
+                "230", "23099", "23000", "23001", "23002", "23003", "23004", "23005", "23006", "23007", "23008",
+                "231", "23199", "23100", "23101", "23102", "23103", "23104", "23105", "23107", "23108", "23109", "23110", "23112", "23113", "23114", "23115", "23116", "23117",
+                "232", "23299", "23201", "23202", "23203", "23204", "23205", "23206", "23207", "23208", "23209", "23210", "23211",
+                "233", "23399", "23301", "23302", "23303", "23304", "23305", "23306", "23307", "23308", "23309", "23310",
+                "234",
+                "235", "23599", "23501", "23502", "23503", "23504", "23505", "23506", "23507", "23508", "23509", "23510", "23511", "23512", "23513", "23514", "23515", "23516", "23517", "23519", "23520", "23521",
+                "236",
+                "237", "23799", "23701", "23707", "23708", "23709", "23710", "23711", "23712",
+                "238", "23899", "23801", "23802", "23803", "23804", "23805", "23806", "23807", "23808", "23809", "23810", "23811", "23812", "23813", "23814", "23815", "23816",
+                "239", "23999", "23901", "23902", "23903", "23904", "23905", "23906", "23907", "23908", "23909", "23910", "23911", "23912", "23913", "23914", "23915", "23916",
+                "240", "24099", "24001", "24002", "24003", "24004", "24005", "24006", "24007", "24008", "24009", "24010", "24011"
+        );
+    }
+
+    public static List<String> transformCkList(List<String> ckList) {
+        List<String> list = new ArrayList<>();
+        if (ObjectUtil.isNotEmpty(ckList)) {
+            for (String str : ckList) {
+                if (!"[]".equals(str)) {
+                    String newStr = str.replace("[", "").replace("]", "").replace(" ","");
+                    List<String> stringList = StrUtil.split(newStr, ",");
+
+                    if (ObjectUtil.isNotEmpty(stringList)) {
+                        list.addAll(stringList);
+                    }
+                }
+            }
+        }
+        return list;
+    }
+
+
+
+
+
+
+
+
+    public static String computeSum(String num1, String num2) {
+        int sum = NumberUtil.parseInt(num1) + NumberUtil.parseInt(num2);
+        return String.valueOf(sum);
+    }
+
+    public static String computeAvg(String sum, String num) {
+        try {
+            BigDecimal sumValue = new BigDecimal(sum);
+            BigDecimal numValue = new BigDecimal(num);
+            double avg = sumValue.divide(numValue, 2, RoundingMode.HALF_UP).doubleValue();
+            return String.valueOf(avg);
+        } catch (Exception e) {
+            return "0";
+        }
+    }
+
+    public static String computeAvg(long sum, String num) {
+        try {
+            BigDecimal sumValue = new BigDecimal(sum);
+            BigDecimal numValue = new BigDecimal(num);
+            double avg = sumValue.divide(numValue, 2, RoundingMode.HALF_UP).doubleValue();
+            return String.valueOf(avg);
+        } catch (Exception e) {
+            return "0";
+        }
+    }
+
+    public static String computeAvg(String sum, int num) {
+        try {
+            BigDecimal sumValue = new BigDecimal(sum);
+            BigDecimal numValue = new BigDecimal(num);
+            double avg = sumValue.divide(numValue, 2, RoundingMode.HALF_UP).doubleValue();
+            return String.valueOf(avg);
+        } catch (Exception e) {
+            return "0";
+        }
+
+    }
+
+    //查找list1有,list2没有的数据
+    public static <T> List<T> getUnContainedListByGeneric(List<T> list1, List<T> list2) {
+        List<T> unContainedList = new ArrayList<>();
+        if (ObjectUtil.isEmpty(list1)) {
+            return unContainedList;
+        }
+        if (ObjectUtil.isEmpty(list2)) {
+            return list1;
+        }
+        for (T user : list1) {
+            for (T userRetained : list2) {
+                if (!list2.contains(userRetained)) {
+                    unContainedList.add(user);
+                }
+
+            }
+        }
+        return unContainedList;
+    }
+
+    //查找list1有,list2没有的数据
+    public static List<String> getUnContainedList(List<String> list1, List<String> list2) {
+        List<String> unContainedList = new ArrayList<>();
+        if (ObjectUtil.isEmpty(list1)) {
+            return unContainedList;
+        }
+        if (ObjectUtil.isEmpty(list2)) {
+            return list1;
+        }
+        Map<String, String> tempMap = list2.parallelStream().collect(Collectors.toMap(Function.identity(), Function.identity(), (oldData, newData) -> newData));
+        return list1.parallelStream().filter(str -> !tempMap.containsKey(str)).collect(Collectors.toList());
+    }
+    public static void main(String[] args) {
+        List<String> list1 = Arrays.asList("a", "b", "c","f");
+        List<String> list2 = Arrays.asList("b","a","e");
+        list1=intersectList(list1,list2);
+        System.out.println(list1);
+    }
+
+    public static String formatRegionName(String name) {
+        String province = null;
+        if (name == null) {
+            return null;
+        } else if (name.endsWith("壮族自治区分公司")) {
+            province = name.replace("壮族自治区分公司", "");
+        } else if (name.endsWith("回族自治区分公司")) {
+            province = name.replace("回族自治区分公司", "");
+        } else if (name.endsWith("维吾尔自治区分公司")) {
+            province = name.replace("维吾尔自治区分公司", "");
+        } else if (name.endsWith("自治区分公司")) {
+            province = name.replace("自治区分公司", "");
+        } else if (name.endsWith("省分公司")) {
+            province = name.replace("省分公司", "");
+        } else if (name.endsWith("市分公司")) {
+            province = name.replace("市分公司", "");
+        } else if (name.equals("全部地区")) {
+            province = "合计";
+        } else if (name.endsWith("分公司")) {
+            province = name.replace("分公司", "");
+        } else if (name.endsWith("地区")) {
+            province = name.replace("地区", "");
+        }else {
+            province = name;
+        }
+        return province;
+    }
+
+
+    //根据时间生成结束时间
+    public static String getEndTime(String time) {
+        Date date = DateUtil.parse(time, "yyyy-MM-dd");
+        Date endTime = DateUtil.offset(date, DateField.DAY_OF_YEAR, 1);
+        return DateUtil.format(endTime, "yyyy-MM-dd HH:mm:ss");
+    }
+
+    //根据时间生成开始时间
+    public static String getStartTime(String time, String type) {
+        Date date = DateUtil.parse(time, "yyyy-MM-dd");
+        Date startTime = null;
+        if (type.equals("W")) {
+            startTime = DateUtil.offset(date, DateField.DAY_OF_YEAR, -6);
+        } else if (type.equals("M")) {
+            startTime = DateUtil.offset(date, DateField.DAY_OF_YEAR, -29);
+        } else if (type.equals("D")) {
+            startTime = DateUtil.offset(date, DateField.DAY_OF_YEAR, 0);
+        }
+        return DateUtil.format(startTime, "yyyy-MM-dd HH:mm:ss");
+    }
+
+    //根据时间生成上一周期开始时间
+    public static String getBeforeTime(String time, String type) {
+        Date date = DateUtil.parse(time, "yyyy-MM-dd");
+        Date startTime = null;
+        if (type.equals("W")) {
+            startTime = DateUtil.offset(date, DateField.DAY_OF_YEAR, -13);
+        } else if (type.equals("M")) {
+            startTime = DateUtil.offset(date, DateField.DAY_OF_YEAR, -59);
+        } else if (type.equals("D")) {
+            startTime = DateUtil.offset(date, DateField.DAY_OF_YEAR, -1);
+        }
+        return DateUtil.format(startTime, "yyyy-MM-dd HH:mm:ss");
+    }
+
+
+    //合并列表
+    public static <T> List<T> mergeList(List<T> list1, List<T> list2) {
+        List<T> list = new ArrayList<>();
+        if (ObjectUtil.isNotEmpty(list1)) {
+            list.addAll(list1);
+        }
+        if (ObjectUtil.isNotEmpty(list2)) {
+            list.addAll(list2);
+        }
+        return list;
+    }
+
+    public static <T> List<T> mergeList(List<T> list1, List<T> list2, List<T> list3) {
+        List<T> list = new ArrayList<>();
+        if (ObjectUtil.isNotEmpty(list1)) {
+            list.addAll(list1);
+        }
+        if (ObjectUtil.isNotEmpty(list2)) {
+            list.addAll(list2);
+        }
+        if (ObjectUtil.isNotEmpty(list3)) {
+            list.addAll(list3);
+        }
+        return list;
+    }
+
+
+    //查找list1有,list2没有的数据
+    public static List<String> subList(List<String> list1, List<String> list2) {
+        Map<String, String> tempMap = list2.parallelStream().collect(Collectors.toMap(Function.identity(), Function.identity(), (oldData, newData) -> newData));
+        return list1.parallelStream().filter(str -> !tempMap.containsKey(str)).collect(Collectors.toList());
+    }
+
+    //查找list1、list2的交集
+    public static List<String> intersectList(List<String> list1, List<String> list2) {
+        if (ObjectUtil.isEmpty(list2)) {
+            return null;
+        }
+        Map<String, String> tempMap = list2.parallelStream().collect(Collectors.toMap(Function.identity(), Function.identity(), (oldData, newData) -> newData));
+        return list1.parallelStream().filter(tempMap::containsKey).collect(Collectors.toList());
+    }
+
+
+    //计算平均值
+    public static String computeAvg(double totalApplicationScore, int num) {
+
+        BigDecimal total = BigDecimal.valueOf(totalApplicationScore);
+        BigDecimal number = BigDecimal.valueOf(num);
+        if (number.compareTo(BigDecimal.valueOf(0)) != 0) {
+
+            double result = total.divide(number, 2, RoundingMode.HALF_UP).doubleValue();
+            return String.valueOf(result);
+        } else {
+            return "0.00";
+        }
+    }
+
+    //计算乘积
+    public static String multiplyNumbers(String num1, String num2) {
+        try {
+            BigDecimal number1 = new BigDecimal(num1);
+            BigDecimal number2 = new BigDecimal(num2);
+            BigDecimal result = number1.multiply(number2).setScale(0, RoundingMode.HALF_UP); // 四舍五入取整数
+            return result.toString();
+        } catch (Exception e) {
+            return "0.00"; // 出现异常时返回默认值
+        }
+    }
+
+    public static String subtractNumbers(String num1, String num2) {
+        try {
+            BigDecimal number1 = new BigDecimal(num1);
+            BigDecimal number2 = new BigDecimal(num2);
+            BigDecimal result = number1.subtract(number2).setScale(2, RoundingMode.HALF_UP); // 保留两位小数
+            return result.toString();
+        } catch (Exception e) {
+            return "0.00"; // 出现异常时返回默认值
+        }
+    }
+
+
+    //合并列表
+    public static <T> List<T> addList(List<T> list1, List<T> list2) {
+
+        if (ObjectUtil.isNotEmpty(list2)) {
+            list1.addAll(list2);
+        }
+        return list1;
+    }
+
+
+    //计算环比
+    public static String computeHb(String before, String now) {
+        try {
+            BigDecimal beforeValue = BigDecimal.valueOf(NumberUtil.parseFloat(before));
+            BigDecimal nowValue = BigDecimal.valueOf(NumberUtil.parseFloat(now));
+            if (beforeValue.compareTo(BigDecimal.valueOf(0)) != 0) {
+                //被除数
+                BigDecimal dividend = nowValue.subtract(beforeValue);
+                double result = dividend.multiply(hundred).divide(beforeValue, 2, RoundingMode.HALF_UP).doubleValue();
+                return String.valueOf(result) + "%";
+            } else {
+                return "0.00%";
+            }
+        } catch (Exception exception) {
+            return "0.00%";
+        }
+    }
+
+    //计算环比
+    public static String computeHb(Integer before, Integer now) {
+        try {
+            BigDecimal beforeValue = BigDecimal.valueOf(before);
+            BigDecimal nowValue = BigDecimal.valueOf(now);
+            if (beforeValue.compareTo(BigDecimal.valueOf(0)) != 0) {
+                //被除数
+                BigDecimal dividend = nowValue.subtract(beforeValue);
+                double result = dividend.multiply(hundred).divide(beforeValue, 2, RoundingMode.HALF_UP).doubleValue();
+                return String.valueOf(result) + "%";
+            } else {
+                return "0.00%";
+            }
+        } catch (Exception exception) {
+            return "0.00%";
+        }
+    }
+
+    //计算环比
+    public static String computeHb(double before, double now) {
+
+        try {
+            BigDecimal beforeValue = BigDecimal.valueOf(before);
+            BigDecimal nowValue = BigDecimal.valueOf(now);
+            if (beforeValue.compareTo(BigDecimal.valueOf(0)) != 0) {
+                //被除数
+                BigDecimal dividend = nowValue.subtract(beforeValue);
+                double result = dividend.multiply(hundred).divide(beforeValue, 2, RoundingMode.HALF_UP).doubleValue();
+                return String.valueOf(result) + "%";
+            } else {
+                return "0.00%";
+            }
+        } catch (Exception exception) {
+            return "0.00%";
+        }
+    }
+
+    //计算占比
+    public static String computeZb(String now, String total) {
+        try {
+            BigDecimal nowValue = BigDecimal.valueOf(NumberUtil.parseFloat(now));
+            BigDecimal totalValue = BigDecimal.valueOf(NumberUtil.parseFloat(total));
+            if (totalValue.compareTo(BigDecimal.valueOf(0)) != 0) {
+                double result = nowValue.multiply(hundred).divide(totalValue, 2, RoundingMode.HALF_UP).doubleValue();
+                return String.valueOf(result) + "%";
+            } else {
+                return "0.00%";
+            }
+        } catch (Exception exception) {
+            return "0.00%";
+        }
+    }
+
+
+    //计算占比
+    public static String computeZb(Integer now, Integer total) {
+        try {
+            BigDecimal nowValue = BigDecimal.valueOf(now);
+            BigDecimal totalValue = BigDecimal.valueOf(total);
+            if (totalValue.compareTo(BigDecimal.valueOf(0)) != 0) {
+                double result = nowValue.multiply(hundred).divide(totalValue, 2, RoundingMode.HALF_UP).doubleValue();
+                return String.valueOf(result) + "%";
+            } else {
+                return "0.00%";
+            }
+        } catch (Exception exception) {
+            return "0.00%";
+        }
+    }
+
+
+    //计算环比
+    public static BigDecimal computeHbValue(float beforeValue, float nowValue) {
+        BigDecimal before = BigDecimal.valueOf(beforeValue);
+        BigDecimal now = BigDecimal.valueOf(nowValue);
+        if (before.compareTo(BigDecimal.valueOf(0)) != 0) {
+            //被除数
+            BigDecimal dividend = now.subtract(before);
+            //环比或同比值
+            return dividend.multiply(hundred).divide(before, 2, RoundingMode.HALF_UP);
+        } else {
+            return new BigDecimal(0);
+        }
+
+    }
+
+
+    //计算占比
+    public static BigDecimal computeZbValue(String value, Float totalValue) {
+        if (totalValue == null) {
+            return null;
+        }
+        if (value == null) {
+            return null;
+        }
+
+        BigDecimal dividend = BigDecimal.valueOf(NumberUtil.parseFloat(value));
+        BigDecimal total = BigDecimal.valueOf(totalValue);
+        //环比或同比值
+        BigDecimal percent = dividend.multiply(hundred).divide(total, 2, RoundingMode.HALF_UP);
+
+        return percent;
+    }
+
+
+
+    public static String getBeforeAggregationTime(String time, String type, String timeFormat) {
+        if ("D".equals(type)) {
+            return DateUtil.format(DateUtil.offsetDay(DateUtil.parse(time, timeFormat), -1), timeFormat);
+        } else if ("W".equals(type)) {
+            return DateUtil.format(DateUtil.offsetWeek(DateUtil.parse(time, timeFormat), -1), timeFormat);
+        } else if ("M".equals(type)) {
+            return DateUtil.format(DateUtil.offsetMonth(DateUtil.parse(time, timeFormat), -1), timeFormat);
+        }
+        return null;
+    }
+
+    //获取上上周期的时间
+    public static String getTwoDurationBeforeAggregationTime(String time, String type, String timeFormat) {
+        if ("D".equals(type)) {
+            return DateUtil.format(DateUtil.offsetDay(DateUtil.parse(time, timeFormat), -2), timeFormat);
+        } else if ("W".equals(type)) {
+            return DateUtil.format(DateUtil.offsetWeek(DateUtil.parse(time, timeFormat), -2), timeFormat);
+        } else if ("M".equals(type)) {
+            return DateUtil.format(DateUtil.offsetMonth(DateUtil.parse(time, timeFormat), -2), timeFormat);
+        }
+        return null;
+    }
+
+    //获取下一天
+    public static String getNextDay(String time, String timeFormat) {
+        String nextDay = DateUtil.format(DateUtil.offsetDay(DateUtil.parse(time, timeFormat), 1), timeFormat);
+        return nextDay;
+    }
+
+    //时间偏移
+    public static String offsetDay(String time, int offsetNum, String timeFormat) {
+        return DateUtil.format(DateUtil.offsetDay(DateUtil.parse(time, timeFormat), offsetNum), timeFormat);
+    }
+
+    //获取去年时间
+    public static String lastYear(String time) {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+        LocalDate date = LocalDate.parse(time, formatter);
+        LocalDate lastYearStartDate = date.minusYears(1);
+        return lastYearStartDate.format(formatter);
+    }
+
+    //根据起止时间生成时间列表
+    public static List<String> getAggregationTimeList(String startTime, String endTime, String timeFormat) {
+        Date start = DateUtil.parse(startTime, timeFormat);
+        Date end = DateUtil.parse(endTime, timeFormat);
+        Date temp = start;
+
+        List<String> timeList = new ArrayList<>();
+        while (temp.before(end)) {
+            timeList.add(DateUtil.format(temp, timeFormat));
+            temp = DateUtil.offsetDay(temp, 1);
+        }
+        timeList.add(endTime);
+        return timeList;
+    }
+
+
+    //根据起止时间生成前一周期时间列表
+    public static List<String> getBeforeAggregationTimeList(String startTime, String endTime, String timeFormat) {
+        Date start = DateUtil.parse(startTime, timeFormat);
+        Date end = DateUtil.parse(endTime, timeFormat);
+        Date temp = start;
+
+        List<String> timeList = new ArrayList<>();
+        while (temp.before(end)) {
+            timeList.add(DateUtil.format(temp, timeFormat));
+            temp = DateUtil.offsetDay(temp, 1);
+        }
+        timeList.add(endTime);
+        return timeList;
+    }
+
+    public static List<String> checkListNull(List<String> list) {
+        if (ObjectUtil.isNotEmpty(list)) {
+            return list;
+        } else {
+            return new ArrayList<>();
+        }
+    }
+
+    public static double convertPercentageToDouble(String percentage) {
+        // 去除字符串末尾的百分号
+        String numberStr = percentage.replace("%", "");
+        try {
+            // 将去除百分号后的字符串转换为 double 类型
+            double number = Double.parseDouble(numberStr);
+            // 将得到的数值除以 100 以得到对应的小数形式
+            return number / 100;
+        } catch (NumberFormatException e) {
+            // 若转换过程中出现异常,打印错误信息并返回 0
+            System.err.println("输入的字符串无法转换为有效的数字: " + percentage);
+            return 0;
+        }
+    }
+
+    public static String formatRateValue(String value) {
+        try {
+            if (value == null) {
+                return "0.00%";
+            }
+            if (value.equals("Infinity")) {
+                return "0.00%";
+            }
+            if (value.equals("NaN")) {
+                return "0.00%";
+            }
+            float result = NumberUtil.parseFloat(value) * 100;
+            return String.format("%.2f", result)+"%";
+        } catch (Exception e) {
+            return "0.00%";
+        }
+    }
+
+    public static String getParentRegionCode(String parentRegionCode) {
+        if (parentRegionCode.equals("110")) {
+            return "all";
+        } else if (parentRegionCode.equals("")) {
+            return "all";
+        } else {
+            return parentRegionCode;
+        }
+
+    }
+
+
+    public static int getDayNumByType(String type) {
+        if (type.equals("M")) {
+            return 30;
+        } else if (type.equals("W")) {
+            return 7;
+        } else {
+            return 1;
+        }
+    }
+
+
+    //用户数/(正式员工基数+代维用户数+非网络线人数)
+    public static String computePenetrationRate(int userNoRepeatNum, int formalBaseNum, int userAgentNum, int notNetworkCableNum) {
+
+        BigDecimal sum = BigDecimal.valueOf(formalBaseNum).add(BigDecimal.valueOf(userAgentNum)).add(BigDecimal.valueOf(notNetworkCableNum));
+
+        BigDecimal num = BigDecimal.valueOf(userNoRepeatNum);
+
+        if (userNoRepeatNum == 0) {
+            return "0";
+        }
+
+        if (sum.compareTo(BigDecimal.valueOf(0)) != 0) {
+            float result = num.multiply(hundred).divide(sum, 2, RoundingMode.HALF_UP).floatValue();
+            return String.valueOf(result);
+        }
+
+        return "0";
+    }
+
+
+
+    //格式化数据
+    public static String numberFormatPercent(String number) {
+        try {
+            boolean isNumFlag = ReUtil.isMatch(regex, number);
+
+            if (isNumFlag) {
+                NumberFormat nf = NumberFormat.getPercentInstance();
+                nf.setMaximumFractionDigits(2);
+                return nf.format(Double.parseDouble(number));
+            }
+
+            if (number.endsWith("%")) {
+                return number;
+            }
+
+        } catch (Exception ignored) {
+            return "- -";
+        }
+        return "0.00%";
+    }
+
+}

+ 175 - 0
snowy-plugin/snowy-plugin-sys/snowy-plugin-sys-func/src/main/java/vip/xiaonuo/sys/modular/user/util/DateTimeUtil.java

@@ -0,0 +1,175 @@
+package vip.xiaonuo.sys.modular.user.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.Duration;
+import java.util.*;
+
+/**
+ * @version 3.5.0
+ * @description: The type Date time util.
+ * Copyright (C), 2020-2024, 武汉思维跳跃科技有限公司
+ * @date 2021/12/25 9:45
+ */
+public class DateTimeUtil {
+
+    private static final Logger logger = LoggerFactory.getLogger(DateTimeUtil.class);
+    /**
+     * The constant STANDER_FORMAT.
+     */
+    public static final String STANDER_FORMAT = "yyyy-MM-dd HH:mm:ss";
+    /**
+     * The constant STANDER_SHORT_FORMAT.
+     */
+    public static final String STANDER_SHORT_FORMAT = "yyyy-MM-dd";
+
+    /**
+     * Add duration date.
+     *
+     * @param date     the date
+     * @param duration the duration
+     * @return the date
+     */
+    public static Date addDuration(Date date, Duration duration) {
+        Calendar ca = Calendar.getInstance();
+        ca.setTime(date);
+        ca.add(Calendar.SECOND, (int) duration.getSeconds());
+        return ca.getTime();
+    }
+
+    /**
+     * Date format string.
+     *
+     * @param date the date
+     * @return the string
+     */
+    public static String dateFormat(Date date) {
+        if (null == date) {
+            return "";
+        }
+        DateFormat dateFormat = new SimpleDateFormat(STANDER_FORMAT);
+        return dateFormat.format(date);
+    }
+
+    /**
+     * Date short format string.
+     *
+     * @param date the date
+     * @return the string
+     */
+    public static String dateShortFormat(Date date) {
+        if (null == date) {
+            return "";
+        }
+        DateFormat dateFormat = new SimpleDateFormat(STANDER_SHORT_FORMAT);
+        return dateFormat.format(date);
+    }
+
+    /**
+     * Parse date.
+     *
+     * @param dateStr the date str
+     * @param format  the format
+     * @return the date
+     */
+    public static Date parse(String dateStr, String format) {
+        try {
+            return new SimpleDateFormat(format).parse(dateStr);
+        } catch (ParseException e) {
+            logger.error(e.getMessage(), e);
+        }
+        return null;
+    }
+
+    /**
+     * Gets month start day.
+     *
+     * @return the month start day
+     */
+    public static Date getMonthStartDay() {
+        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd 00:00:00");
+        Calendar cale = Calendar.getInstance();
+        cale.add(Calendar.MONTH, 0);
+        cale.set(Calendar.DAY_OF_MONTH, 1);
+        String dateStr = formatter.format(cale.getTime());
+        return parse(dateStr, "yyyy-MM-dd HH:mm:ss");
+    }
+
+    /**
+     * Gets month end day.
+     *
+     * @return the month end day
+     */
+    public static Date getMonthEndDay() {
+        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd 23:59:59");
+        Calendar cale = Calendar.getInstance();
+        cale.add(Calendar.MONTH, 1);
+        cale.set(Calendar.DAY_OF_MONTH, 0);
+        String dateStr = formatter.format(cale.getTime());
+        return parse(dateStr, STANDER_FORMAT);
+    }
+
+
+    /**
+     * Moth start to now format list.
+     *
+     * @return the list
+     */
+    public static List<String> MothStartToNowFormat() {
+        Date startTime = getMonthStartDay();
+        Calendar nowCalendar = Calendar.getInstance();
+        nowCalendar.setTime(new Date());
+        int mothDayCount = nowCalendar.get(Calendar.DAY_OF_MONTH);
+        List<String> mothDays = new ArrayList<>(mothDayCount);
+        Calendar startCalendar = new GregorianCalendar();
+        startCalendar.setTime(startTime);
+        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
+        mothDays.add(formatter.format(startTime));
+        for (int i = 0; i < mothDayCount - 1; i++) {
+            startCalendar.add(Calendar.DATE, 1);
+            Date end_date = startCalendar.getTime();
+            mothDays.add(formatter.format(end_date));
+        }
+        return mothDays;
+    }
+
+
+    /**
+     * Moth day list.
+     *
+     * @return the list
+     */
+    public static List<String> MothDay() {
+        Calendar endCalendar = Calendar.getInstance();
+        endCalendar.setTime(getMonthEndDay());
+        int endMothDay = endCalendar.get(Calendar.DAY_OF_MONTH);
+        List<String> list = new ArrayList<>(endMothDay);
+        for (int i = 1; i <= endMothDay; i++) {
+            list.add(String.valueOf(i));
+        }
+        return list;
+    }
+
+    /**
+     * 根据考试结束时间生成cron表达式
+     * @param endTime 考试结束时间
+     * @return cron表达式
+     */
+    public static String generateCronExpression(Date endTime) {
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(endTime);
+
+        int minute = calendar.get(Calendar.MINUTE);
+        int hour = calendar.get(Calendar.HOUR_OF_DAY);
+        int day = calendar.get(Calendar.DAY_OF_MONTH);
+        int month = calendar.get(Calendar.MONTH) + 1; // Calendar.MONTH是从0开始的
+        int year = calendar.get(Calendar.YEAR);
+
+        // 生成类似 "0 minute hour day month ? year" 的cron表达式
+        return String.format("0 %d %d %d %d ? %d", minute, hour, day, month, year);
+    }
+}