Lin Qilong 2 месяцев назад
Родитель
Сommit
73f60f3c25

+ 0 - 1
plugins/disabled.txt

@@ -1 +0,0 @@
-biz-plugin

+ 69 - 21
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java

@@ -1,32 +1,38 @@
 package com.ruoyi.web.controller.system;
 
-import java.util.List;
-import java.util.Set;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RestController;
 import com.ruoyi.common.constant.Constants;
 import com.ruoyi.common.core.domain.AjaxResult;
 import com.ruoyi.common.core.domain.entity.SysMenu;
 import com.ruoyi.common.core.domain.entity.SysUser;
 import com.ruoyi.common.core.domain.model.LoginBody;
 import com.ruoyi.common.core.domain.model.LoginUser;
+import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.framework.web.service.SysLoginService;
 import com.ruoyi.framework.web.service.SysPermissionService;
 import com.ruoyi.framework.web.service.TokenService;
+import com.ruoyi.system.service.ISysConfigService;
 import com.ruoyi.system.service.ISysMenuService;
+import com.ruoyi.system.service.ISysUserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
 
 /**
  * 登录验证
- * 
+ *
  * @author ruoyi
  */
 @RestController
-public class SysLoginController
-{
+public class SysLoginController {
     @Autowired
     private SysLoginService loginService;
 
@@ -39,15 +45,20 @@ public class SysLoginController
     @Autowired
     private TokenService tokenService;
 
+    @Autowired
+    private ISysUserService userService;
+
+    @Autowired
+    private ISysConfigService configService;
+
     /**
      * 登录方法
-     * 
+     *
      * @param loginBody 登录信息
      * @return 结果
      */
     @PostMapping("/login")
-    public AjaxResult login(@RequestBody LoginBody loginBody)
-    {
+    public AjaxResult login(@RequestBody LoginBody loginBody) {
         AjaxResult ajax = AjaxResult.success();
         // 生成令牌
         String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),
@@ -58,23 +69,25 @@ public class SysLoginController
 
     /**
      * 获取用户信息
-     * 
+     *
      * @return 用户信息
      */
     @GetMapping("getInfo")
-    public AjaxResult getInfo()
-    {
+    public AjaxResult getInfo() {
         LoginUser loginUser = SecurityUtils.getLoginUser();
         SysUser user = loginUser.getUser();
         // 角色集合
         Set<String> roles = permissionService.getRolePermission(user);
         // 权限集合
         Set<String> permissions = permissionService.getMenuPermission(user);
-        if (!loginUser.getPermissions().equals(permissions))
-        {
+        if (!loginUser.getPermissions().equals(permissions)) {
             loginUser.setPermissions(permissions);
             tokenService.refreshToken(loginUser);
         }
+
+        // 在 return token; 之前,添加密码过期检查
+        checkPasswordExpire(loginUser);
+
         AjaxResult ajax = AjaxResult.success();
         ajax.put("user", user);
         ajax.put("roles", roles);
@@ -82,14 +95,49 @@ public class SysLoginController
         return ajax;
     }
 
+    /**
+     * 检查密码是否过期
+     */
+    private void checkPasswordExpire(LoginUser loginUser) {
+        SysUser user = userService.selectUserById(loginUser.getUserId());
+
+        // 1. 从系统参数获取密码有效期配置
+        String validateDaysStr = configService.selectConfigByKey("sys.user.passwordValidateDays");
+        // 如果没配置,默认为0,即永不过期
+        int validateDays = StringUtils.isNumeric(validateDaysStr) ? Integer.parseInt(validateDaysStr) : 0;
+
+        if (validateDays > 0) {
+            // 2. 计算密码的最后有效日期
+            Date pwdUpdateTime = user.getPwdUpdateTime();
+            if (pwdUpdateTime == null) {
+                // 如果记录为空,则认为是超级老的用户,强制过期
+                pwdUpdateTime = user.getCreateTime();
+            }
+            Calendar calendar = Calendar.getInstance();
+            calendar.setTime(pwdUpdateTime);
+            calendar.add(Calendar.DAY_OF_MONTH, validateDays);
+            Date expireTime = calendar.getTime();
+            Date now = new Date();
+
+            // 3. 判断当前时间是否晚于过期时间
+            if (now.after(expireTime)) {
+                // 4. 如果过期,抛出异常,提示用户密码已过期
+                // 这里使用一个自定义异常,前端捕获后跳转到修改密码页面
+                throw new ServiceException("用户密码已过期,请修改密码后再登录");
+                // 或者,更友好的方式是:记录一个状态,让前端引导用户去修改密码
+                // 例如:AsyncManager.me().execute(AsyncFactory.recordLogininfor(..."密码已过期"...));
+                // throw new RuntimeException("password expired");
+            }
+        }
+    }
+
     /**
      * 获取路由信息
-     * 
+     *
      * @return 路由信息
      */
     @GetMapping("getRouters")
-    public AjaxResult getRouters()
-    {
+    public AjaxResult getRouters() {
         Long userId = SecurityUtils.getUserId();
         List<SysMenu> menus = menuService.selectMenuTreeByUserId(userId);
         return AjaxResult.success(menuService.buildMenus(menus));

+ 0 - 6
ruoyi-admin/src/main/resources/application-dev.yml

@@ -4,12 +4,6 @@ ruoyi:
 sys:
   gateway:
     url: http://localhost:8081
-  report:
-    upload:
-      path: /soft/sh-model/report/
-  chart:
-    upload:
-      path: /soft/sh-model/chart/
   map:
     upload:
       path: /soft/sh-model/map/

+ 3 - 9
ruoyi-admin/src/main/resources/application-dm.yml

@@ -4,15 +4,9 @@ ruoyi:
 sys:
   gateway:
     url: http://localhost:8081
-    report:
-      upload:
-        path: /soft/sh-model/report/
-    chart:
-      upload:
-        path: /soft/sh-model/chart/
-    map:
-      upload:
-        path: /soft/sh-model/map/
+  map:
+    upload:
+      path: /soft/sh-model/map/
 # 数据源配置
 spring:
   datasource:

+ 3 - 9
ruoyi-admin/src/main/resources/application-druid.yml

@@ -4,15 +4,9 @@ ruoyi:
 sys:
   gateway:
     url: http://localhost:8081
-    report:
-      upload:
-        path: /soft/sh-model/report/
-    chart:
-      upload:
-        path: /soft/sh-model/chart/
-    map:
-      upload:
-        path: /soft/sh-model/map/
+  map:
+    upload:
+      path: /soft/sh-model/map/
 # 数据源配置
 spring:
   datasource:

+ 0 - 252
ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/controller/PtChartController.java

@@ -1,252 +0,0 @@
-package com.ruoyi.interfaces.controller;
-
-import com.github.pagehelper.PageHelper;
-import com.github.pagehelper.PageInfo;
-import com.ruoyi.common.core.controller.BaseController;
-import com.ruoyi.common.core.domain.AjaxResult;
-import com.ruoyi.common.utils.uuid.IdUtils;
-import com.ruoyi.interfaces.core.page.Page;
-import com.ruoyi.interfaces.core.page.PageUtils;
-import com.ruoyi.interfaces.domain.PtChart;
-import com.ruoyi.interfaces.domain.PtChartFile;
-import com.ruoyi.interfaces.domain.SysCate;
-import com.ruoyi.interfaces.mapper.PtChartFileMapper;
-import com.ruoyi.interfaces.mapper.PtChartMapper;
-import com.ruoyi.interfaces.service.SysCateService;
-import io.swagger.annotations.ApiOperation;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.multipart.MultipartFile;
-import org.springframework.web.servlet.ModelAndView;
-
-import java.io.File;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.List;
-
-@RestController
-@RequestMapping("/pt/chart/")
-public class PtChartController extends BaseController {
-    private static Logger logger = LoggerFactory.getLogger(PtChartController.class);
-
-    @Autowired
-    private PtChartMapper mapMapper;
-
-    @Autowired
-    private PtChartFileMapper ptChartFileMapper;
-
-    @Autowired
-    private SysCateService sysCateService;
-
-    @Value("${sys.chart.upload.path}")
-    private String uploadFilePath;
-
-
-    private SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
-
-    @ApiOperation("添加数据")
-    @RequestMapping(value = "/add", method = RequestMethod.POST)
-    public AjaxResult add(@RequestParam(name = "file", required = false) MultipartFile file, @RequestParam(name = "image", required = false) MultipartFile image, PtChart map) {
-
-        if (image != null) {
-            //第一步:写入文件
-            String imageName = image.getOriginalFilename();
-            String suffix = imageName.substring(imageName.lastIndexOf(".") + 1);
-            String uuid = IdUtils.simpleUUID();
-            try {
-                String tmpPath = sdf.format(new Date());
-                String filePath = tmpPath + "/" + uuid + "." + suffix;
-                //1、判断目录。如果目录不存在,则创建目录
-                File dir = new File(uploadFilePath + filePath);
-                if (!dir.exists()) {
-                    dir.mkdirs();
-                }
-                //2、写入文件
-                image.transferTo(new File(uploadFilePath + filePath));
-                map.setThumb(imageName);// 缩略图名称
-                map.setUrl(filePath);// 缩略图地址
-            } catch (Exception e) {
-                //在此处调用
-                logger.error(e.getLocalizedMessage(), e);
-                return AjaxResult.error();
-            }
-        }
-
-        String code = IdUtils.simpleUUID();
-        map.setCode(code);
-        map.setPubUser("测试ID");
-        map.setPubTime(new Date());
-        System.out.println(map);
-        int ret = mapMapper.insert(map);
-
-        if (file != null) {
-            //第一步:写入文件
-            String fileName = file.getOriginalFilename();
-            String suffix = fileName.substring(fileName.lastIndexOf(".") + 1);
-            String uuid = IdUtils.simpleUUID();
-            try {
-                String tmpPath = sdf.format(new Date());
-                String filePath = uploadFilePath + "/" + tmpPath + "/" + uuid + "." + suffix;
-                //1、判断目录。如果目录不存在,则创建目录
-                File dir = new File(filePath);
-                if (!dir.exists()) {
-                    dir.mkdirs();
-                }
-                //2、写入文件
-                file.transferTo(new File(filePath));
-                // 3、写入数据库
-                PtChartFile ptChartFile = new PtChartFile();
-                ptChartFile.setFileId(IdUtils.simpleUUID());
-                ptChartFile.setCode(code);
-                ptChartFile.setRelId(code);
-                ptChartFile.setFileType(suffix);
-                ptChartFile.setFilePath(filePath);
-                ptChartFile.setFileSavename(uuid);
-                ptChartFile.setFileViewname(fileName);
-                ptChartFile.setFileTime(new Date());
-                ptChartFileMapper.insert(ptChartFile);
-            } catch (Exception e) {
-                logger.error(e.getLocalizedMessage(), e);
-                return AjaxResult.error();
-            }
-        }
-        return AjaxResult.success();
-    }
-
-    @ApiOperation("根据主键获取数据")
-    @RequestMapping(value = "/get")
-    public ModelAndView get(@RequestParam(required = true) String key) {
-        ModelAndView mav = new ModelAndView();
-        PtChart ptChart = mapMapper.selectByPrimaryKey(key);
-        if (ptChart != null) {
-            mav.addObject("ptChart", ptChart);
-        }
-        PtChartFile ptChartFile = ptChartFileMapper.selectByCode(key);
-        if (ptChartFile != null) {
-            mav.addObject("fileViewname", ptChartFile.getFileViewname());
-            mav.addObject("fileId", ptChartFile.getFileId());
-        }
-        mav.setViewName("mng/pt_chart_edit");
-        return mav;
-
-    }
-
-    @ApiOperation("数据列表")
-    @RequestMapping(value = "/list")
-    public Page getList(@RequestParam(required = false, defaultValue = "1") int page,
-                        @RequestParam(required = false, defaultValue = "100") int rows
-            , PtChart ptChart) {
-        System.out.println(ptChart);
-        PageHelper.startPage(page, rows);
-        PageHelper.orderBy("PUB_TIME desc");
-        List<PtChart> list = mapMapper.selectAll(ptChart);
-        PageInfo<PtChart> pageInfo = new PageInfo<PtChart>(list);
-        return PageUtils.convert(pageInfo);
-    }
-
-    @ApiOperation("根据主键更新数据")
-    @RequestMapping(value = "/update", method = RequestMethod.POST)
-    public AjaxResult update(@RequestParam(name = "file", required = false) MultipartFile file, @RequestParam(name = "smallImage", required = false) MultipartFile smallImage, PtChart map, String fileId) {
-        // 上传缩略图
-        if (smallImage != null) {
-            //第一步:写入文件
-            String imageName = smallImage.getOriginalFilename();
-            String suffix = imageName.substring(imageName.lastIndexOf(".") + 1);
-            String uuid = IdUtils.simpleUUID();
-            try {
-                String tmpPath = sdf.format(new Date());
-                String filePath = tmpPath + "/" + uuid + "." + suffix;
-                //1、判断目录。如果目录不存在,则创建目录
-                File dir = new File(uploadFilePath + filePath);
-                if (!dir.exists()) {
-                    dir.mkdirs();
-                }
-                //2、写入文件
-                smallImage.transferTo(new File(uploadFilePath + filePath));
-                map.setThumb(imageName);// 缩略图名称
-                map.setUrl(filePath);// 缩略图地址
-            } catch (Exception e) {
-                logger.error(e.getLocalizedMessage(), e);
-                return AjaxResult.error();
-            }
-        }
-        // 修改主表
-        map.setPubTime(new Date());
-        int ret = mapMapper.updateByPrimaryKey(map);
-        // 上传文件
-        if (file != null) {
-            //第一步:写入文件
-            String fileName = file.getOriginalFilename();
-            String suffix = fileName.substring(fileName.lastIndexOf(".") + 1);
-            String uuid = IdUtils.simpleUUID();
-            try {
-                String tmpPath = sdf.format(new Date());
-                String filePath = uploadFilePath + tmpPath + "/" + uuid + "." + suffix;
-                //1、判断目录。如果目录不存在,则创建目录
-                File dir = new File(filePath);
-                if (!dir.exists()) {
-                    dir.mkdirs();
-                }
-                //2、写入文件
-                file.transferTo(new File(filePath));
-                // 3、写入数据库
-                PtChartFile ptChartFile = new PtChartFile();
-                ptChartFile.setFileId(fileId);
-                ptChartFile.setFileType(suffix);
-                ptChartFile.setFilePath(filePath);
-                ptChartFile.setFileSavename(uuid);
-                ptChartFile.setFileViewname(fileName);
-                ptChartFile.setFileTime(new Date());
-                if (fileId == null || "".equals(fileId)) {
-                    ptChartFile.setFileId(IdUtils.simpleUUID());
-                    ptChartFile.setCode(map.getCode());
-                    ptChartFile.setRelId(map.getCode());
-                    ptChartFileMapper.insert(ptChartFile);
-                } else {
-                    ptChartFileMapper.updateByPrimaryKeySelective(ptChartFile);
-                }
-            } catch (Exception e) {
-                logger.error(e.getLocalizedMessage(), e);
-                return AjaxResult.error();
-            }
-        }
-        return AjaxResult.success();
-
-
-    }
-
-    @ApiOperation("根据主键删除数据")
-    @RequestMapping(value = "/delete", method = RequestMethod.GET)
-    public AjaxResult delete(@RequestParam(required = true) String id) {
-        int ret0 = ptChartFileMapper.deleteByCode(id);
-        int ret = mapMapper.deleteByPrimaryKey(id);
-        return AjaxResult.success();
-    }
-
-    @ApiOperation("去查看图表视图页面")
-    @RequestMapping("/to_select_chart")
-    public ModelAndView toSelectChart(String code) {
-        ModelAndView modelAndView = new ModelAndView();
-        PtChart ptChart = mapMapper.findById(code);
-        //ptChart.setChartMess(JSON.toJSONString(eChartList3()));
-        //ptChart.setCateSeries(JSON.toJSONString(echartList().getSeries()));
-        modelAndView.addObject("ptChart", ptChart);
-        modelAndView.setViewName("mng/pt_chart_preview");
-        return modelAndView;
-    }
-
-    @ApiOperation("数据列表")
-    @RequestMapping(value = "/cate_list", method = RequestMethod.GET)
-    public List<SysCate> list(String tabName) {
-        SysCate criteria = new SysCate();
-        criteria.setCateTable(tabName);
-        return sysCateService.findAllO(criteria);//.selectByCriteria(criteria);//查询
-    }
-
-}

+ 0 - 399
ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/controller/PtDataImpController.java

@@ -1,399 +0,0 @@
-package com.ruoyi.interfaces.controller;
-
-import com.alibaba.fastjson2.JSONObject;
-import com.github.pagehelper.PageHelper;
-import com.github.pagehelper.PageInfo;
-import com.ruoyi.common.core.controller.BaseController;
-import com.ruoyi.common.core.domain.AjaxResult;
-import com.ruoyi.common.utils.uuid.IdUtils;
-import com.ruoyi.interfaces.core.page.Page;
-import com.ruoyi.interfaces.core.page.PageUtils;
-import com.ruoyi.interfaces.domain.PtDataImp;
-import com.ruoyi.interfaces.domain.PtDataImpField;
-import com.ruoyi.interfaces.domain.SysCate;
-import com.ruoyi.interfaces.mapper.PtDataImpMapper;
-import com.ruoyi.interfaces.service.SysCateService;
-import io.swagger.annotations.ApiOperation;
-import org.apache.poi.hssf.usermodel.HSSFWorkbook;
-import org.apache.poi.ss.usermodel.Row;
-import org.apache.poi.ss.usermodel.Sheet;
-import org.apache.poi.ss.usermodel.Workbook;
-import org.apache.poi.xssf.usermodel.XSSFWorkbook;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.multipart.MultipartFile;
-import org.springframework.web.servlet.ModelAndView;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.*;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-
-@RestController
-@RequestMapping(value = "/pt/data/imp")
-public class PtDataImpController extends BaseController {
-    private static final Logger logger = LoggerFactory.getLogger(PtDataImpController.class);
-
-    @Autowired
-    private PtDataImpMapper ptDatImpMapper;
-
-    @Autowired
-    private SysCateService sysCateService;
-
-    @ApiOperation("添加数据导入")
-    @RequestMapping(value = "/insert", method = RequestMethod.POST)
-    public AjaxResult insert(PtDataImp p, String json) {
-        p.setId(IdUtils.simpleUUID());
-        int insert = ptDatImpMapper.insert(p);
-        if (insert > 0) {
-            //添加详情
-            if (json != null && !"".equals(json)) {
-                String[] strs = json.split(";");
-                for (String str : strs) {
-                    PtDataImpField ptDataImpField = JSONObject.parseObject(str, PtDataImpField.class);
-                    ptDataImpField.setId(p.getId());
-                    ptDataImpField.setFid(IdUtils.simpleUUID());
-                    ptDatImpMapper.insertPtDatImpField(ptDataImpField);
-                }
-            }
-            return AjaxResult.success(p.getId());
-        }
-        return AjaxResult.error();
-    }
-
-    @ApiOperation("查询数据导入")
-    @RequestMapping(value = "/list", method = RequestMethod.GET)
-    public Page selectAll(int page, int rows, PtDataImp ptDataImp) {
-        PageHelper.startPage(page, rows);
-        List<PtDataImp> list = ptDatImpMapper.selectAll(ptDataImp);
-        PageInfo<PtDataImp> pageInfo = new PageInfo<>(list);
-        return PageUtils.convert(pageInfo);
-    }
-
-    @ApiOperation("修改数据导入")
-    @RequestMapping(value = "/update", method = RequestMethod.POST)
-    public AjaxResult update(PtDataImp p, String json) {
-        System.out.println(p);
-        int update = ptDatImpMapper.update(p);
-        System.out.println("update" + update);
-        //修改详情
-        PtDataImpField ptDataImpField2 = new PtDataImpField();
-        ptDataImpField2.setId(p.getId());
-        if (update > 0) {
-            ptDatImpMapper.deletePtDataImpField(ptDataImpField2);
-            if (json != null && !"".equals(json)) {
-                String[] strs = json.split(";");
-                for (String str : strs) {
-                    PtDataImpField ptDataImpField = JSONObject.parseObject(str, PtDataImpField.class);
-                    ptDataImpField.setId(p.getId());
-                    ptDataImpField.setFid(IdUtils.simpleUUID());
-                    ptDatImpMapper.insertPtDatImpField(ptDataImpField);
-                }
-            }
-            return AjaxResult.success();
-        }
-        return AjaxResult.error();
-    }
-
-    @ApiOperation("删除数据导入")
-    @RequestMapping(value = "/delete", method = RequestMethod.GET)
-    public AjaxResult delete(String ids) {
-        String[] strs = ids.split(",");
-        int deletes = 0;
-        for (String str : strs) {
-            //查询是否存在子类
-            PtDataImpField pt = new PtDataImpField();
-            pt.setId(str);
-            ptDatImpMapper.deletePtDataImpField(pt);
-            deletes += ptDatImpMapper.delete(str);
-        }
-        if (deletes == strs.length) {
-            return AjaxResult.success();
-        }
-        return AjaxResult.error();
-    }
-
-    @ApiOperation("删除操作")
-    @RequestMapping(value = "/delete_imp_field")
-    public AjaxResult deleteImpField(PtDataImpField pf) {
-        int delete = ptDatImpMapper.deletePtDataImpField(pf);
-        if (delete > 0) {
-            return AjaxResult.success();
-        }
-        return AjaxResult.error();
-    }
-
-    @ApiOperation("跳到数据导入修改页面")
-    @RequestMapping(value = "/to_update", method = RequestMethod.GET)
-    public ModelAndView toUpdatePtDataImpField(String id, String impType) {
-        ModelAndView model = new ModelAndView();
-        model.addObject("ptDataImp", ptDatImpMapper.findByOne(id));
-        model.addObject("data", impExcelList);
-        model.addObject("impType", impType);
-        model.setViewName("mng/pt_data_imp_edit");
-        return model;
-    }
-
-    @ApiOperation("根据数据导入查询导入字段")
-    @RequestMapping(value = "/select_imp_field")
-    public List<PtDataImpField> selectField(PtDataImpField p) {
-        List<PtDataImpField> list = null;
-        list = ptDatImpMapper.selectAllPtDatImpField(p);
-        return list;
-    }
-
-
-    @ApiOperation("数据列表")
-    @RequestMapping(value = "/cate_list", method = RequestMethod.GET)
-    public List<SysCate> list(String tabName) {
-        SysCate criteria = new SysCate();
-        criteria.setCateTable(tabName);
-        return sysCateService.findAllO(criteria);//.selectByCriteria(criteria);//查询
-    }
-
-    @ApiOperation("导出数据到本地")
-    @RequestMapping(value = "/export_excel", method = RequestMethod.POST)
-    public AjaxResult exportExcel(String id, String fileName) {
-        PtDataImpField ptDataImpField = new PtDataImpField();
-        ptDataImpField.setId(id);
-        List<PtDataImpField> list = ptDatImpMapper.selectAllPtDatImpField(ptDataImpField);
-        if (list.size() == 0) {
-            //AjaxResult.error("模板无内容");
-        }
-        return AjaxResult.success(expExcel(list));
-
-    }
-
-    private SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
-    @Value("${sys.report.upload.path}")
-    private String reportPath;
-
-    public String expExcel(List<PtDataImpField> list) {
-        String fileName = "数据导入模板" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
-        String path = "E:\\excel文件\\" + fileName + ".xls";
-
-        String tmpPath = sdf.format(new Date());
-        String filePath = reportPath + tmpPath;
-        //  清空临时文件夹
-        OutputStream os = null;
-        Workbook book = null;
-
-        try {
-            if (!new File(filePath).exists()) {
-                new File(filePath).mkdirs();
-            }
-            String id = IdUtils.simpleUUID();
-            // 输出流
-            os = new FileOutputStream(filePath + "/" + id + ".xls");
-            // 创建工作区(97-2003)
-            book = new HSSFWorkbook();
-            // 创建第一个sheet页
-            Sheet sheet = book.createSheet(fileName);
-            // 生成行数,对应的list集合长度
-            for (int i = 0; i < 2; i++) {
-                Row row = sheet.createRow(i);
-                //给每一行的每一列赋值
-                if (i == 0) {
-                    for (int j = 0; j < list.size(); j++) {
-                        row.createCell(j).setCellValue(list.get(j).getCode());
-                    }
-                }
-                if (i == 1) {
-                    for (int j = 0; j < list.size(); j++) {
-                        row.createCell(j).setCellValue(list.get(j).getName());
-                    }
-                }
-            }
-            // 写文件
-            book.write(os);
-            /*http://127.0.0.1:8083/gxpt/report/2018/09/19/1042325997479800832.xlsx*/
-            return tmpPath + "/" + id + ".xls";
-        } catch (FileNotFoundException e) {
-            return "";
-        } catch (IOException e) {
-            return "";
-        } finally {
-            // 关闭输出流
-            try {
-                os.close();
-            } catch (IOException e) {
-                logger.error(e.getLocalizedMessage(), e);
-            }
-        }
-
-    }
-
-    private static List<PtDataImpField> impExcelList = null;
-
-    @ApiOperation("从本地导入数据到数据库")
-    @RequestMapping(value = "/import_excel", method = RequestMethod.POST)
-    public AjaxResult impExcel(@RequestParam("file") MultipartFile file, HttpServletResponse response, HttpServletRequest request) throws IOException {
-        String path = "E:\\excel文件";
-        //  清空临时文件夹
-//        FileUtils.deleteDir(new File(path));
-        String suffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".") + 1);
-        String uuid = IdUtils.simpleUUID();
-        //1、判断目录。如果目录不存在,则创建目录
-        File dir = new File(path);
-        if (!dir.exists()) {
-            dir.mkdirs();
-        }
-        //2、写入文件
-        file.transferTo(new File(path + "/" + uuid + "." + suffix));
-        List<PtDataImpField> impExcelList = impExcel(path + "/" + uuid + "." + suffix);
-        return AjaxResult.success(impExcelList);
-    }
-
-    /**
-     * 导入Excel
-     *
-     * @param
-     */
-    public List<PtDataImpField> impExcel(String inputStream) {
-        List<PtDataImpField> list = new ArrayList<>();
-        try {
-            // 构造 Workbook 对象,execelFile 是传入文件路径(获得Excel工作区)
-            Workbook book = null;
-            try {
-                // Excel 2007获取方法
-                book = new XSSFWorkbook(new FileInputStream(inputStream));
-            } catch (Exception ex) {
-                // Excel 2003获取方法
-                book = new HSSFWorkbook(new FileInputStream(inputStream));
-            }
-
-            // 读取表格的第一个sheet页
-            Sheet sheet = book.getSheetAt(0);
-
-            // 定义 row、cell
-            Row row;
-            String cell;
-            // 总共有多少行,从0开始
-            int totalRows = 1;
-
-            // 循环输出表格中的内容,首先循环取出行,再根据行循环取出列
-            for (int i = 0; i < totalRows; i++) {
-                row = sheet.getRow(i);
-                // 处理空行
-                if (row == null) {
-                    continue;
-                }
-                // 总共有多少列,从0开始
-                int totalCells = row.getLastCellNum();
-                for (int j = row.getFirstCellNum(); j < totalCells; j++) {
-                    PtDataImpField ptDataImpField = new PtDataImpField();
-                    // 获取单元格内容
-                    //  cell = sheet.getRow(0).getCell(j).toString();
-                    // cell = sheet.getRow(0).getCell(j).toString();
-                    ptDataImpField.setCode(sheet.getRow(0).getCell(j).toString());
-                    ptDataImpField.setName(sheet.getRow(1).getCell(j).toString());
-
-                    ptDataImpField.setType("1");
-                    ptDataImpField.setPosX(j + 1);
-                    ptDataImpField.setSeq(j + 1);
-                    ptDataImpField.setPosY(0);
-                    ptDataImpField.setLength("0");
-                    ptDataImpField.setId(list.size() + "");
-                    list.add(ptDataImpField);
-                }
-            }
-        } catch (FileNotFoundException e) {
-            logger.error(e.getLocalizedMessage(), e);
-        } catch (IOException e) {
-            logger.error(e.getLocalizedMessage(), e);
-        }
-        return list;
-    }
-
-    @ApiOperation("导入数据进入数据库")
-    @RequestMapping(value = "/import_data", method = RequestMethod.POST)
-    public AjaxResult impModel(@RequestParam("file") MultipartFile file, HttpServletResponse response, HttpServletRequest request, PtDataImp ptDataImp) throws IOException {
-        String path = "E:\\excel文件";
-        //  清空临时文件夹
-//        FileUtils.deleteDir(new File(path));
-        String suffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".") + 1);
-        String uuid = IdUtils.simpleUUID();
-        //1、判断目录。如果目录不存在,则创建目录
-        File dir = new File(path);
-        if (!dir.exists()) {
-            dir.mkdirs();
-        }
-        //2、写入文件
-        file.transferTo(new File(path + "/" + uuid + "." + suffix));
-        return impModel(path + "/" + uuid + "." + suffix, ptDatImpMapper.findByOne(ptDataImp.getId()));
-    }
-
-    /**
-     * 导入模板
-     *
-     * @param
-     */
-    public AjaxResult impModel(String inputStream, PtDataImp ptDataImp) {
-        try {
-            Workbook book = null;
-            try {
-                // Excel 2007获取方法
-                book = new XSSFWorkbook(new FileInputStream(inputStream));
-            } catch (Exception ex) {
-                // Excel 2003获取方法
-                book = new HSSFWorkbook(new FileInputStream(inputStream));
-            }
-
-            /*根据配置的行、列进行读取Excel*/
-            //开始行
-            int startRow = ptDataImp.getStartRow() - 1;
-            //开始列
-            int startCol = ptDataImp.getStartCol() - 1;
-            //结束行
-            int endRow = ptDataImp.getEndRow() - 1;
-            //结束列
-            int endCol = ptDataImp.getEndCol();
-
-            // 读取表格的第一个sheet页
-            Sheet sheet = book.getSheetAt(0);
-            Row row;
-            String cell;
-            // 循环输出表格中的内容,首先循环取出行,再根据行循环取出列
-//            for (int i = startRow; i <= endRow; i++) {
-//                row = sheet.getRow(i);
-//                if (row == null) {
-//                    continue;
-//                }
-//                Test test = new Test();
-//                for (int j = startCol; j < endCol; j++) {
-//                    // 处理空列
-//                    if (row.getCell(j) == null) {
-//                        continue;
-//                    }
-//                    // 通过 row.getCell(j).toString() 获取单元格内容
-//                    cell = row.getCell(j).toString();
-//                    if (j == 0) {
-//                        test.setId(cell);
-//                    }
-//                    if (j == 1) {
-//                        test.setName(cell);
-//                    }
-//                    System.out.print(cell + "\t");
-//                }
-//                if (test.getId() != null || test.getName() != null) {
-//                    ptDatImpMapper.insertTest(test);
-//                }
-//            }
-        } catch (FileNotFoundException e) {
-            return AjaxResult.error();
-        } catch (IOException e) {
-            return AjaxResult.error();
-        }
-        return AjaxResult.success();
-    }
-
-}

+ 0 - 286
ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/controller/PtReportController.java

@@ -1,286 +0,0 @@
-package com.ruoyi.interfaces.controller;
-
-import com.github.pagehelper.PageHelper;
-import com.github.pagehelper.PageInfo;
-import com.ruoyi.common.core.controller.BaseController;
-import com.ruoyi.common.core.domain.AjaxResult;
-import com.ruoyi.common.utils.uuid.IdUtils;
-import com.ruoyi.interfaces.core.page.PageUtils;
-import com.ruoyi.interfaces.domain.PtReport;
-import com.ruoyi.interfaces.domain.PtReportFile;
-import com.ruoyi.interfaces.domain.SysCate;
-import com.ruoyi.interfaces.mapper.PtReportDAO;
-import com.ruoyi.interfaces.mapper.PtReportFileMapper;
-import com.ruoyi.interfaces.mapper.SqlMapper;
-import com.ruoyi.interfaces.service.SysCateService;
-import io.swagger.annotations.ApiOperation;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.transaction.annotation.Transactional;
-import org.springframework.web.bind.annotation.*;
-import org.springframework.web.multipart.MultipartFile;
-import org.springframework.web.servlet.ModelAndView;
-
-import javax.servlet.http.HttpServletRequest;
-import java.io.File;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.List;
-
-@RestController
-@RequestMapping("/pt/report")
-public class PtReportController extends BaseController {
-    private static Logger logger = LoggerFactory.getLogger(PtReportController.class);
-
-    @Value("${sys.report.upload.path}")
-    private String reportPath;
-
-    @Autowired
-    private PtReportDAO BDAO;
-
-    @Autowired
-    private SysCateService sysCateService;
-    @Autowired
-    private PtReportFileMapper PFMapper;
-
-    @ApiOperation("添加报表服务")
-    @RequestMapping(value = "/add", method = RequestMethod.POST)
-    public AjaxResult insertBaoBiaoFuWu(PtReport bao) {
-        bao.setCode(IdUtils.simpleUUID());
-        bao.setPubTime(new Date());
-        bao.setPubUser("");
-        int success = BDAO.insert(bao);
-        if (success > 0) {
-            AjaxResult b = AjaxResult.success();
-            return b;
-        }
-        return AjaxResult.error();
-    }
-
-    @ApiOperation("数据列表")
-    @RequestMapping(value = "/list", method = RequestMethod.GET)
-    @ResponseBody
-    public com.ruoyi.interfaces.core.page.Page getList(@RequestParam(required = false, defaultValue = "1") int page,
-                                                       @RequestParam(required = false, defaultValue = "100") int rows, PtReport ptReport) {
-        PageHelper.startPage(page, rows);
-        PageHelper.orderBy("PUB_TIME desc");
-        List<PtReport> list = BDAO.selectAll(ptReport);//dao.(criteria);//查询
-        // 取分页信息
-        PageInfo<PtReport> pageInfo = new PageInfo<PtReport>(list);
-        return PageUtils.convert(pageInfo);
-    }
-
-    @ApiOperation("去编辑页面")
-    @RequestMapping(value = "/to_edit", method = RequestMethod.GET)
-    public ModelAndView toEdit(String code) {
-        ModelAndView model = new ModelAndView();
-        PtReport pt = new PtReport();
-        pt.setCode(code);
-        model.addObject("ptReport", BDAO.selectAll(pt).get(0));
-        //查询是否存在文件
-        model.addObject("ptPeportFile", PFMapper.selectCodeFile(code));
-        model.setViewName("mng/pt_report_edit");
-        return model;
-    }
-
-    @ApiOperation("修改报表服务")
-    @RequestMapping(value = "/edit", method = RequestMethod.POST)
-    public AjaxResult updateptReoprt(PtReport bao) {
-        bao.setPubTime(new Date());
-        bao.setPubUser("");
-        int update = BDAO.update(bao);
-        if (update > 0) {
-            return AjaxResult.success();
-        }
-        return AjaxResult.error();
-    }
-
-    @ApiOperation("批量删除")
-    @RequestMapping(value = "/delete", method = RequestMethod.GET)
-    @Transactional
-    public AjaxResult deletePtReport(String codes) {
-        String[] strs = codes.split(",");
-        int deletes = 0;
-        for (String str : strs) {
-            //删除之前先查附件,有附件也删除,防止出现子记录
-            PtReportFile pf = PFMapper.selectCodeFile(str);
-            if (pf != null) {
-                int deleteFile = PFMapper.deleteByPrimaryKey(pf.getFileId());
-            }
-            int delete = BDAO.delete(str);
-            deletes += delete;
-        }
-        if (deletes == strs.length) {
-            return AjaxResult.success();
-        }
-        return AjaxResult.error();
-    }
-
-
-    private SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
-
-    @ApiOperation("文件导入")
-    @RequestMapping(value = "/import", method = RequestMethod.POST)
-    public AjaxResult importExcel(@RequestParam("file") MultipartFile file, HttpServletRequest request, PtReport bao) {
-
-        //第一步:写入文件
-        String fileName = file.getOriginalFilename();
-        //如果不是空的,就新增文件
-        if (fileName != null && !"".equals(fileName)) {
-            String suffix = fileName.substring(fileName.lastIndexOf(".") + 1);
-            String uuid = IdUtils.simpleUUID();
-            try {
-                String tmpPath = sdf.format(new Date());
-                String filePath = reportPath + tmpPath;
-                //1、判断目录。如果目录不存在,则创建目录
-                File dir = new File(filePath);
-                if (!dir.exists()) {
-                    dir.mkdirs();
-                }
-                //2、写入文件
-                file.transferTo(new File(filePath + "/" + uuid + "." + suffix));
-
-                //第二步:解析文件,返回值为List<T>
-
-                PtReportFile ptReportFile = new PtReportFile();
-                //第三步:写入数据库
-                bao.setCode(IdUtils.simpleUUID());
-                bao.setPubTime(new Date());
-                bao.setPubUser("");
-                int success = BDAO.insert(bao);
-                if (success > 0) {
-                    ptReportFile.setFileId(IdUtils.simpleUUID());
-                    ptReportFile.setFileTime(new Date());
-                    ptReportFile.setFileType(suffix);
-                    ptReportFile.setFilePath(filePath + "/" + uuid + "." + suffix);
-                    ptReportFile.setFileSavename(uuid);
-                    ptReportFile.setFileViewname(fileName);
-                    ptReportFile.setRelId("1");
-                    ptReportFile.setCode(bao.getCode());
-                    int insert = PFMapper.insert(ptReportFile);
-                    if (insert > 0) {
-                        return AjaxResult.success();
-                    }
-                    return AjaxResult.error();
-                }
-            } catch (Exception e) {
-                //在此处调用
-                logger.error(e.getLocalizedMessage(), e);
-                return AjaxResult.error();
-            }
-        } else {
-            bao.setCode(IdUtils.simpleUUID());
-            bao.setPubTime(new Date());
-            bao.setPubUser("");
-            int success = BDAO.insert(bao);
-            if (success > 0) {
-                return AjaxResult.success();
-            }
-            return AjaxResult.error();
-        }
-        return AjaxResult.error();
-    }
-
-    @ApiOperation("文件导入")
-    @RequestMapping(value = "/edit_import", method = RequestMethod.POST)
-    public AjaxResult editImport(@RequestParam("file") MultipartFile file, HttpServletRequest request, PtReport bao, String fileId, String fileViewName) {
-
-        //第一步:写入文件
-        String fileName = file.getOriginalFilename();
-        //如果不是空的,就新增文件
-        if (fileName != null && !"".equals(fileName)) {
-            String suffix = fileName.substring(fileName.lastIndexOf(".") + 1);
-            String uuid = IdUtils.simpleUUID();
-            try {
-                String tmpPath = sdf.format(new Date());
-                String filePath = reportPath + tmpPath;
-                //1、判断目录。如果目录不存在,则创建目录
-                File dir = new File(filePath);
-                if (!dir.exists()) {
-                    dir.mkdirs();
-                }
-                //2、写入文件
-                file.transferTo(new File(filePath + "/" + uuid + "." + suffix));
-
-                //第二步:解析文件,返回值为List<T>
-
-                PtReportFile ptReportFile = new PtReportFile();
-                //第三步:写入数据库
-                bao.setPubTime(new Date());
-                bao.setPubUser("");
-                int update = BDAO.update(bao);
-                if (update > 0) {
-                    ptReportFile.setFileId(fileId);
-                    ptReportFile.setFileTime(new Date());
-                    ptReportFile.setFileType(suffix);
-                    ptReportFile.setFilePath(filePath + "/" + uuid + "." + suffix);
-                    ptReportFile.setFileSavename(uuid);
-                    ptReportFile.setFileViewname(fileName);
-                    ptReportFile.setRelId("1");
-                    ptReportFile.setCode(bao.getCode());
-                    ptReportFile.setFileTime(new Date());
-                    int update2 = PFMapper.updateByPrimaryKey(ptReportFile);
-                    if (update2 > 0) {
-                        return AjaxResult.success();
-                    }
-                    return AjaxResult.error();
-                }
-                return AjaxResult.error();
-            } catch (Exception e) {
-                //在此处调用
-                logger.error(e.getLocalizedMessage(), e);
-                return AjaxResult.error();
-            }
-        } else {
-            //第三步:写入数据库
-            bao.setPubTime(new Date());
-            bao.setPubUser("");
-            int update = BDAO.update(bao);
-            if (update > 0) {
-                return AjaxResult.success();
-            }
-            return AjaxResult.error();
-        }
-    }
-
-    @ApiOperation("数据列表")
-    @RequestMapping(value = "/cate_list", method = RequestMethod.GET)
-    public List<SysCate> list(String tabName) {
-        SysCate criteria = new SysCate();
-        criteria.setCateTable(tabName);
-        return sysCateService.findAllO(criteria);//.selectByCriteria(criteria);//查询
-    }
-
-    @Autowired
-    private SqlMapper sqlMapper;
-
-    /**
-     * 图形格式拼接
-     *
-     * @param
-     * @return
-     */
-    @GetMapping("/to_pt_report_pre")
-    public ModelAndView toPtReportPre(String code) {
-        ModelAndView modelAndView = new ModelAndView();
-        PtReport ptReport = new PtReport();
-        ptReport.setCode(code);
-        PtReport p = BDAO.selectAll(ptReport).get(0);
-        List list = null, list2 = null;
-        if ("混合".equals(p.getStyle())) {
-            list = sqlMapper.selectAll(p.getSql());
-            list2 = sqlMapper.selectAll(p.getListSql());
-        } else if ("图表".equals(p.getStyle())) {
-            list = sqlMapper.selectAll(p.getSql());
-        } else {
-            list2 = sqlMapper.selectAll(p.getListSql());
-        }
-        modelAndView.addObject("list", list);
-        modelAndView.addObject("list2", list2);
-        modelAndView.addObject("ptReport", p);
-        modelAndView.setViewName("mng/pt_report_pre");
-        return modelAndView;
-    }
-}

+ 19 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java

@@ -13,6 +13,14 @@ import com.ruoyi.common.annotation.Excel.Type;
 import com.ruoyi.common.annotation.Excels;
 import com.ruoyi.common.core.domain.BaseEntity;
 import com.ruoyi.common.xss.Xss;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+import javax.validation.constraints.Email;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
+import java.util.Date;
+import java.util.List;
 
 /**
  * 用户对象 sys_user
@@ -136,6 +144,17 @@ public class SysUser extends BaseEntity
         this.dataSignature = dataSignature;
     }
 
+    /** 最后修改密码时间 */
+    private Date pwdUpdateTime;
+
+    public Date getPwdUpdateTime() {
+        return pwdUpdateTime;
+    }
+
+    public void setPwdUpdateTime(Date pwdUpdateTime) {
+        this.pwdUpdateTime = pwdUpdateTime;
+    }
+
     public SysUser()
     {
 

+ 36 - 57
ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java

@@ -1,12 +1,5 @@
 package com.ruoyi.framework.web.service;
 
-import javax.annotation.Resource;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.authentication.BadCredentialsException;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.core.Authentication;
-import org.springframework.stereotype.Component;
 import com.ruoyi.common.constant.CacheConstants;
 import com.ruoyi.common.constant.Constants;
 import com.ruoyi.common.constant.UserConstants;
@@ -14,11 +7,7 @@ import com.ruoyi.common.core.domain.entity.SysUser;
 import com.ruoyi.common.core.domain.model.LoginUser;
 import com.ruoyi.common.core.redis.RedisCache;
 import com.ruoyi.common.exception.ServiceException;
-import com.ruoyi.common.exception.user.BlackListException;
-import com.ruoyi.common.exception.user.CaptchaException;
-import com.ruoyi.common.exception.user.CaptchaExpireException;
-import com.ruoyi.common.exception.user.UserNotExistsException;
-import com.ruoyi.common.exception.user.UserPasswordNotMatchException;
+import com.ruoyi.common.exception.user.*;
 import com.ruoyi.common.utils.DateUtils;
 import com.ruoyi.common.utils.MessageUtils;
 import com.ruoyi.common.utils.StringUtils;
@@ -28,15 +17,22 @@ import com.ruoyi.framework.manager.factory.AsyncFactory;
 import com.ruoyi.framework.security.context.AuthenticationContextHolder;
 import com.ruoyi.system.service.ISysConfigService;
 import com.ruoyi.system.service.ISysUserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
 
 /**
  * 登录校验方法
- * 
+ *
  * @author ruoyi
  */
 @Component
-public class SysLoginService
-{
+public class SysLoginService {
     @Autowired
     private TokenService tokenService;
 
@@ -45,7 +41,7 @@ public class SysLoginService
 
     @Autowired
     private RedisCache redisCache;
-    
+
     @Autowired
     private ISysUserService userService;
 
@@ -54,75 +50,63 @@ public class SysLoginService
 
     /**
      * 登录验证
-     * 
+     *
      * @param username 用户名
      * @param password 密码
-     * @param code 验证码
-     * @param uuid 唯一标识
+     * @param code     验证码
+     * @param uuid     唯一标识
      * @return 结果
      */
-    public String login(String username, String password, String code, String uuid)
-    {
+    public String login(String username, String password, String code, String uuid) {
         // 验证码校验
         validateCaptcha(username, code, uuid);
         // 登录前置校验
         loginPreCheck(username, password);
         // 用户验证
         Authentication authentication = null;
-        try
-        {
+        try {
             UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
             AuthenticationContextHolder.setContext(authenticationToken);
             // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
             authentication = authenticationManager.authenticate(authenticationToken);
-        }
-        catch (Exception e)
-        {
-            if (e instanceof BadCredentialsException)
-            {
+        } catch (Exception e) {
+            if (e instanceof BadCredentialsException) {
                 AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                 throw new UserPasswordNotMatchException();
-            }
-            else
-            {
+            } else {
                 AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
                 throw new ServiceException(e.getMessage());
             }
-        }
-        finally
-        {
+        } finally {
             AuthenticationContextHolder.clearContext();
         }
         AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
         LoginUser loginUser = (LoginUser) authentication.getPrincipal();
         recordLoginInfo(loginUser.getUserId());
+
         // 生成token
         return tokenService.createToken(loginUser);
     }
 
     /**
      * 校验验证码
-     * 
+     *
      * @param username 用户名
-     * @param code 验证码
-     * @param uuid 唯一标识
+     * @param code     验证码
+     * @param uuid     唯一标识
      * @return 结果
      */
-    public void validateCaptcha(String username, String code, String uuid)
-    {
+    public void validateCaptcha(String username, String code, String uuid) {
         boolean captchaEnabled = configService.selectCaptchaEnabled();
-        if (captchaEnabled)
-        {
+        if (captchaEnabled) {
             String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
             String captcha = redisCache.getCacheObject(verifyKey);
-            if (captcha == null)
-            {
+            if (captcha == null) {
                 AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
                 throw new CaptchaExpireException();
             }
             redisCache.deleteObject(verifyKey);
-            if (!code.equalsIgnoreCase(captcha))
-            {
+            if (!code.equalsIgnoreCase(captcha)) {
                 AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
                 throw new CaptchaException();
             }
@@ -131,35 +115,31 @@ public class SysLoginService
 
     /**
      * 登录前置校验
+     *
      * @param username 用户名
      * @param password 用户密码
      */
-    public void loginPreCheck(String username, String password)
-    {
+    public void loginPreCheck(String username, String password) {
         // 用户名或密码为空 错误
-        if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password))
-        {
+        if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
             AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("not.null")));
             throw new UserNotExistsException();
         }
         // 密码如果不在指定范围内 错误
         if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
-                || password.length() > UserConstants.PASSWORD_MAX_LENGTH)
-        {
+                || password.length() > UserConstants.PASSWORD_MAX_LENGTH) {
             AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
             throw new UserPasswordNotMatchException();
         }
         // 用户名不在指定范围内 错误
         if (username.length() < UserConstants.USERNAME_MIN_LENGTH
-                || username.length() > UserConstants.USERNAME_MAX_LENGTH)
-        {
+                || username.length() > UserConstants.USERNAME_MAX_LENGTH) {
             AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
             throw new UserPasswordNotMatchException();
         }
         // IP黑名单校验
         String blackStr = configService.selectConfigByKey("sys.login.blackIPList");
-        if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr()))
-        {
+        if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr())) {
             AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("login.blocked")));
             throw new BlackListException();
         }
@@ -170,8 +150,7 @@ public class SysLoginService
      *
      * @param userId 用户ID
      */
-    public void recordLoginInfo(Long userId)
-    {
+    public void recordLoginInfo(Long userId) {
         SysUser sysUser = new SysUser();
         sysUser.setUserId(userId);
         sysUser.setLoginIp(IpUtils.getIpAddr());

+ 6 - 2
ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml

@@ -23,6 +23,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="updateBy"     column="update_by"    />
         <result property="updateTime"   column="update_time"  />
         <result property="remark"       column="remark"       />
+		<result property="pwdUpdateTime"       column="pwd_update_time"       />
         <association property="dept"    javaType="SysDept"         resultMap="deptResult" />
         <collection  property="roles"   javaType="java.util.List"  resultMap="RoleResult" />
     </resultMap>
@@ -47,7 +48,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </resultMap>
 	
 	<sql id="selectUserVo">
-        select u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, 
+        select u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark,  u.pwd_update_time,
         d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.status as dept_status,
         r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status
         from sys_user u
@@ -160,6 +161,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  			<if test="remark != null and remark != ''">remark,</if>
  			<if test="dataSignature != null and dataSignature != ''">data_Signature,</if>
  			create_time
+ 			create_time,
+			pwd_update_time
  		)values(
  			<if test="userId != null and userId != ''">#{userId},</if>
  			<if test="deptId != null and deptId != ''">#{deptId},</if>
@@ -174,6 +177,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  			<if test="createBy != null and createBy != ''">#{createBy},</if>
  			<if test="remark != null and remark != ''">#{remark},</if>
  			<if test="dataSignature != null and dataSignature != ''">#{dataSignature},</if>
+ 			sysdate(),
  			sysdate()
  		)
 	</insert>
@@ -208,7 +212,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 	</update>
 	
 	<update id="resetUserPwd" parameterType="SysUser">
- 		update sys_user set password = #{password} where user_name = #{userName}
+ 		update sys_user set password = #{password}, pwd_update_time = sysdate() where user_name = #{userName}
 	</update>
 	
 	<delete id="deleteUserById" parameterType="Long">

BIN
ruoyi-ui/src/assets/images/index_bj.png


+ 15 - 7
ruoyi-ui/src/permission.js

@@ -11,7 +11,7 @@ import usePermissionStore from "@/store/modules/permission";
 
 NProgress.configure({ showSpinner: false });
 
-const whiteList = ["/login", "/register", "index"];
+const whiteList = ["/login", "/register", "/forceResetPwd", "index"];
 
 router.beforeEach((to, from, next) => {
   NProgress.start();
@@ -44,12 +44,20 @@ router.beforeEach((to, from, next) => {
               });
           })
           .catch((err) => {
-            useUserStore()
-              .logOut()
-              .then(() => {
-                ElMessage.error(err);
-                next({ path: "/" });
-              });
+            // 捕获错误
+            if (err?.message?.includes("密码已过期")) {
+              // 1. 提示用户
+              ElMessage.error("密码已过期,请立即修改");
+              // 2. 跳转到强制修改密码页面,并将当前用户名带过去
+              next({ path: "/forceResetPwd" });
+            } else {
+                useUserStore()
+                    .logOut()
+                    .then(() => {
+                        ElMessage.error(err);
+                        next({ path: "/" });
+                    });
+            }
           });
       } else {
         next();

+ 5 - 0
ruoyi-ui/src/router/index.js

@@ -37,6 +37,11 @@ export const constantRoutes = [
             }
         ]
     },
+    {
+        path: '/forceResetPwd',
+        component: () => import('@/views/forceResetPwd'),
+        hidden: true,
+    },
     {
         path: '/login',
         component: () => import('@/views/login'),

+ 99 - 0
ruoyi-ui/src/views/forceResetPwd.vue

@@ -0,0 +1,99 @@
+<template>
+  <div class="force-reset-pwd">
+    <div  class="force-reset-pwd-form">
+      <h3 class="title">修改密码</h3>
+      <reset-pwd @close="handleClose" @success="handleSuccess"></reset-pwd>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import ResetPwd from "@/views/system/user/profile/resetPwd.vue";
+import useUserStore from "@/store/modules/user.js";
+
+const {proxy} = getCurrentInstance();
+
+function handleClose() {
+  useUserStore()
+      .logOut()
+      .then(() => {
+        proxy.$router.push({path: "/"});
+      });
+}
+
+function handleSuccess(){
+   proxy.$router.push({path: "/"});
+}
+</script>
+<style lang='scss' scoped>
+.force-reset-pwd {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 100%;
+  background-image: url("../assets/images/index_bj.png");
+  background-size: cover;
+}
+
+.title {
+  margin: 0px auto 30px auto;
+  text-align: center;
+  color: #707070;
+}
+
+.force-reset-pwd-form {
+  border-radius: 6px;
+  background: #ffffff;
+  width: 400px;
+  padding: 25px 25px 5px 25px;
+
+  .el-input {
+    height: 40px;
+
+    input {
+      height: 40px;
+    }
+  }
+
+  .input-icon {
+    height: 39px;
+    width: 14px;
+    margin-left: 0px;
+  }
+}
+
+.login-tip {
+  font-size: 13px;
+  text-align: center;
+  color: #bfbfbf;
+}
+
+.login-code {
+  width: 33%;
+  height: 40px;
+  float: right;
+
+  img {
+    cursor: pointer;
+    vertical-align: middle;
+  }
+}
+
+.el-login-footer {
+  height: 40px;
+  line-height: 40px;
+  position: fixed;
+  bottom: 0;
+  width: 100%;
+  text-align: center;
+  color: #fff;
+  font-family: Arial;
+  font-size: 12px;
+  letter-spacing: 1px;
+}
+
+.login-code-img {
+  height: 40px;
+  padding-left: 12px;
+}
+</style>

+ 1 - 3
ruoyi-ui/src/views/login.vue

@@ -73,7 +73,7 @@
 <script setup>
 import {getCodeImg} from "@/api/login";
 import Cookies from "js-cookie";
-import {encrypt, decrypt} from "@/utils/jsencrypt";
+import {decrypt, encrypt} from "@/utils/jsencrypt";
 import useUserStore from '@/store/modules/user'
 
 const userStore = useUserStore()
@@ -84,8 +84,6 @@ const {proxy} = getCurrentInstance();
 const loginForm = ref({
   username: "",
   password: "",
-  // username: "admin",
-  // password: "Gw#$1601",
   rememberMe: false,
   code: "",
   uuid: ""

+ 1 - 1
ruoyi-ui/src/views/map/components/map.vue

@@ -65,7 +65,7 @@ const initMap = () => {
       maxZoom: 16,
       projection: 'EPSG:4326',
     }),
-    // layers: [vecLayer, cvaLayer],
+    layers: [vecLayer, cvaLayer],
     controls: defaultControls({
       zoom: false,//不显示放大放小按钮
       rotate: false,//不显示指北针控件

+ 0 - 1
ruoyi-ui/src/views/register/modelData/dataQuery.vue

@@ -277,7 +277,6 @@ function handleNodeClick(node, data) {
       params.value = [...queryOptions.params, ...queryOptions.body]
     })
   })
-
 }
 
 function renameTreeProperties(tree) {

+ 1 - 1
ruoyi-ui/src/views/standardization/bizDataShowConfig/show/index.vue

@@ -130,7 +130,7 @@ async function loadTableData(config, page = 1) {
       params[paginationConfig.value.pageNumParam || 'pageNum'] = page
       params[paginationConfig.value.pageSizeParam || 'pageSize'] = paginationConfig.value.pageSize || 10
     }
-    debugger
+    
     let res;
     switch (props.source) {
       case "DATA_SET":

+ 423 - 351
ruoyi-ui/src/views/system/user/index.vue

@@ -1,341 +1,370 @@
 <template>
-   <div class="app-container">
-      <el-row :gutter="20">
-         <!--部门数据-->
-         <el-col :span="4" :xs="24">
-            <div class="head-container">
-               <el-input
-                  v-model="deptName"
-                  placeholder="请输入部门名称"
-                  clearable
-                  prefix-icon="Search"
-                  style="margin-bottom: 20px"
-               />
-            </div>
-            <div class="head-container">
-               <el-tree
-                  :data="deptOptions"
-                  :props="{ label: 'label', children: 'children' }"
-                  :expand-on-click-node="false"
-                  :filter-node-method="filterNode"
-                  ref="deptTreeRef"
-                  node-key="id"
-                  highlight-current
-                  default-expand-all
-                  @node-click="handleNodeClick"
-               />
-            </div>
-         </el-col>
-         <!--用户数据-->
-         <el-col :span="20" :xs="24">
-            <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
-               <el-form-item label="用户名称" prop="userName">
-                  <el-input
-                     v-model="queryParams.userName"
-                     placeholder="请输入用户名称"
-                     clearable
-                     style="width: 240px"
-                     @keyup.enter="handleQuery"
-                  />
-               </el-form-item>
-               <el-form-item label="手机号码" prop="phonenumber">
-                  <el-input
-                     v-model="queryParams.phonenumber"
-                     placeholder="请输入手机号码"
-                     clearable
-                     style="width: 240px"
-                     @keyup.enter="handleQuery"
-                  />
-               </el-form-item>
-               <el-form-item label="状态" prop="status">
-                  <el-select
-                     v-model="queryParams.status"
-                     placeholder="用户状态"
-                     clearable
-                     style="width: 240px"
-                  >
-                     <el-option
-                        v-for="dict in sys_normal_disable"
-                        :key="dict.value"
-                        :label="dict.label"
-                        :value="dict.value"
-                     />
-                  </el-select>
-               </el-form-item>
-               <el-form-item label="创建时间" style="width: 308px;">
-                  <el-date-picker
-                     v-model="dateRange"
-                     value-format="YYYY-MM-DD"
-                     type="daterange"
-                     range-separator="-"
-                     start-placeholder="开始日期"
-                     end-placeholder="结束日期"
-                  ></el-date-picker>
-               </el-form-item>
-               <el-form-item>
-                  <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-                  <el-button icon="Refresh" @click="resetQuery">重置</el-button>
-               </el-form-item>
-            </el-form>
-
-            <el-row :gutter="10" class="mb8">
-               <el-col :span="1.5">
-                  <el-button
-                     type="primary"
-                     plain
-                     icon="Plus"
-                     @click="handleAdd"
-                     v-hasPermi="['system:user:add']"
-                  >新增</el-button>
-               </el-col>
-               <el-col :span="1.5">
-                  <el-button
-                     type="success"
-                     plain
-                     icon="Edit"
-                     :disabled="single"
-                     @click="handleUpdate"
-                     v-hasPermi="['system:user:edit']"
-                  >修改</el-button>
-               </el-col>
-               <el-col :span="1.5">
-                  <el-button
-                     type="danger"
-                     plain
-                     icon="Delete"
-                     :disabled="multiple"
-                     @click="handleDelete"
-                     v-hasPermi="['system:user:remove']"
-                  >删除</el-button>
-               </el-col>
-               <el-col :span="1.5">
-                  <el-button
-                     type="info"
-                     plain
-                     icon="Upload"
-                     @click="handleImport"
-                     v-hasPermi="['system:user:import']"
-                  >导入</el-button>
-               </el-col>
-               <el-col :span="1.5">
-                  <el-button
-                     type="warning"
-                     plain
-                     icon="Download"
-                     @click="handleExport"
-                     v-hasPermi="['system:user:export']"
-                  >导出</el-button>
-               </el-col>
-               <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
-            </el-row>
-
-            <el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
-               <el-table-column type="selection" width="50" align="center" />
-               <el-table-column label="用户编号" align="center" key="userId" prop="userId" v-if="columns[0].visible" />
-               <el-table-column label="用户名称" align="center" key="userName" prop="userName" v-if="columns[1].visible" :show-overflow-tooltip="true" />
-               <el-table-column label="用户昵称" align="center" key="nickName" prop="nickName" v-if="columns[2].visible" :show-overflow-tooltip="true" />
-               <el-table-column label="部门" align="center" key="deptName" prop="dept.deptName" v-if="columns[3].visible" :show-overflow-tooltip="true" />
-               <el-table-column label="手机号码" align="center" key="phonenumber" prop="phonenumber" v-if="columns[4].visible" width="120" />
-               <el-table-column label="状态" align="center" key="status" v-if="columns[5].visible">
-                  <template #default="scope">
-                     <el-switch
-                        v-model="scope.row.status"
-                        active-value="0"
-                        inactive-value="1"
-                        @change="handleStatusChange(scope.row)"
-                     ></el-switch>
-                  </template>
-               </el-table-column>
-               <el-table-column label="创建时间" align="center" prop="createTime" v-if="columns[6].visible" width="160">
-                  <template #default="scope">
-                     <span>{{ parseTime(scope.row.createTime) }}</span>
-                  </template>
-               </el-table-column>
-               <el-table-column label="操作" align="center" width="150" class-name="small-padding fixed-width">
-                  <template #default="scope">
-                     <el-tooltip content="修改" placement="top" v-if="scope.row.userId !== 1">
-                        <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:user:edit']"></el-button>
-                     </el-tooltip>
-                     <el-tooltip content="删除" placement="top" v-if="scope.row.userId !== 1">
-                        <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:user:remove']"></el-button>
-                     </el-tooltip>
-                     <el-tooltip content="重置密码" placement="top" v-if="scope.row.userId !== 1">
-                         <el-button link type="primary" icon="Key" @click="handleResetPwd(scope.row)" v-hasPermi="['system:user:resetPwd']"></el-button>
-                     </el-tooltip>
-                     <el-tooltip content="分配角色" placement="top" v-if="scope.row.userId !== 1">
-                        <el-button link type="primary" icon="CircleCheck" @click="handleAuthRole(scope.row)" v-hasPermi="['system:user:edit']"></el-button>
-                     </el-tooltip>
-                  </template>
-               </el-table-column>
-            </el-table>
-            <pagination
-               v-show="total > 0"
-               :total="total"
-               v-model:page="queryParams.pageNum"
-               v-model:limit="queryParams.pageSize"
-               @pagination="getList"
+  <div class="app-container">
+    <el-row :gutter="20">
+      <!--部门数据-->
+      <el-col :span="4" :xs="24">
+        <div class="head-container">
+          <el-input
+              v-model="deptName"
+              placeholder="请输入部门名称"
+              clearable
+              prefix-icon="Search"
+              style="margin-bottom: 20px"
+          />
+        </div>
+        <div class="head-container">
+          <el-tree
+              :data="deptOptions"
+              :props="{ label: 'label', children: 'children' }"
+              :expand-on-click-node="false"
+              :filter-node-method="filterNode"
+              ref="deptTreeRef"
+              node-key="id"
+              highlight-current
+              default-expand-all
+              @node-click="handleNodeClick"
+          />
+        </div>
+      </el-col>
+      <!--用户数据-->
+      <el-col :span="20" :xs="24">
+        <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
+          <el-form-item label="用户名称" prop="userName">
+            <el-input
+                v-model="queryParams.userName"
+                placeholder="请输入用户名称"
+                clearable
+                style="width: 240px"
+                @keyup.enter="handleQuery"
             />
-         </el-col>
-      </el-row>
-
-      <!-- 添加或修改用户配置对话框 -->
-      <el-dialog :title="title" v-model="open" width="600px" append-to-body>
-         <el-form :model="form" :rules="rules" ref="userRef" label-width="80px">
-            <el-row>
-               <el-col :span="12">
-                  <el-form-item label="用户昵称" prop="nickName">
-                     <el-input v-model="form.nickName" placeholder="请输入用户昵称" maxlength="30" />
-                  </el-form-item>
-               </el-col>
-               <el-col :span="12">
-                  <el-form-item label="归属部门" prop="deptId">
-                     <el-tree-select
-                        v-model="form.deptId"
-                        :data="deptOptions"
-                        :props="{ value: 'id', label: 'label', children: 'children' }"
-                        value-key="id"
-                        placeholder="请选择归属部门"
-                        check-strictly
-                     />
-                  </el-form-item>
-               </el-col>
-            </el-row>
-            <el-row>
-               <el-col :span="12">
-                  <el-form-item label="手机号码" prop="phonenumber">
-                     <el-input v-model="form.phonenumber" placeholder="请输入手机号码" maxlength="11" />
-                  </el-form-item>
-               </el-col>
-               <el-col :span="12">
-                  <el-form-item label="邮箱" prop="email">
-                     <el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" />
-                  </el-form-item>
-               </el-col>
-            </el-row>
-            <el-row>
-               <el-col :span="12">
-                  <el-form-item v-if="form.userId == undefined" label="用户名称" prop="userName">
-                     <el-input v-model="form.userName" placeholder="请输入用户名称" maxlength="30" />
-                  </el-form-item>
-               </el-col>
-               <el-col :span="12">
-                  <el-form-item v-if="form.userId == undefined" label="用户密码" prop="password">
-                     <el-input v-model="form.password" placeholder="请输入用户密码" type="password" maxlength="20" show-password />
-                  </el-form-item>
-               </el-col>
-            </el-row>
-            <el-row>
-               <el-col :span="12">
-                  <el-form-item label="用户性别">
-                     <el-select v-model="form.sex" placeholder="请选择">
-                        <el-option
-                           v-for="dict in sys_user_sex"
-                           :key="dict.value"
-                           :label="dict.label"
-                           :value="dict.value"
-                        ></el-option>
-                     </el-select>
-                  </el-form-item>
-               </el-col>
-               <el-col :span="12">
-                  <el-form-item label="状态">
-                     <el-radio-group v-model="form.status">
-                        <el-radio
-                           v-for="dict in sys_normal_disable"
-                           :key="dict.value"
-                           :value="dict.value"
-                        >{{ dict.label }}</el-radio>
-                     </el-radio-group>
-                  </el-form-item>
-               </el-col>
-            </el-row>
-            <el-row>
-               <el-col :span="12">
-                  <el-form-item label="岗位">
-                     <el-select v-model="form.postIds" multiple placeholder="请选择">
-                        <el-option
-                           v-for="item in postOptions"
-                           :key="item.postId"
-                           :label="item.postName"
-                           :value="item.postId"
-                           :disabled="item.status == 1"
-                        ></el-option>
-                     </el-select>
-                  </el-form-item>
-               </el-col>
-               <el-col :span="12">
-                  <el-form-item label="角色">
-                     <el-select v-model="form.roleIds" multiple placeholder="请选择">
-                        <el-option
-                           v-for="item in roleOptions"
-                           :key="item.roleId"
-                           :label="item.roleName"
-                           :value="item.roleId"
-                           :disabled="item.status == 1"
-                        ></el-option>
-                     </el-select>
-                  </el-form-item>
-               </el-col>
-            </el-row>
-            <el-row>
-               <el-col :span="24">
-                  <el-form-item label="备注">
-                     <el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
-                  </el-form-item>
-               </el-col>
-            </el-row>
-         </el-form>
-         <template #footer>
-            <div class="dialog-footer">
-               <el-button type="primary" @click="submitForm">确 定</el-button>
-               <el-button @click="cancel">取 消</el-button>
-            </div>
-         </template>
-      </el-dialog>
-
-      <!-- 用户导入对话框 -->
-      <el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body>
-         <el-upload
-            ref="uploadRef"
-            :limit="1"
-            accept=".xlsx, .xls"
-            :headers="upload.headers"
-            :action="upload.url + '?updateSupport=' + upload.updateSupport"
-            :disabled="upload.isUploading"
-            :on-progress="handleFileUploadProgress"
-            :on-success="handleFileSuccess"
-            :auto-upload="false"
-            drag
-         >
-            <el-icon class="el-icon--upload"><upload-filled /></el-icon>
-            <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
-            <template #tip>
-               <div class="el-upload__tip text-center">
-                  <div class="el-upload__tip">
-                     <el-checkbox v-model="upload.updateSupport" />是否更新已经存在的用户数据
-                  </div>
-                  <span>仅允许导入xls、xlsx格式文件。</span>
-                  <el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;" @click="importTemplate">下载模板</el-link>
-               </div>
+          </el-form-item>
+          <el-form-item label="手机号码" prop="phonenumber">
+            <el-input
+                v-model="queryParams.phonenumber"
+                placeholder="请输入手机号码"
+                clearable
+                style="width: 240px"
+                @keyup.enter="handleQuery"
+            />
+          </el-form-item>
+          <el-form-item label="状态" prop="status">
+            <el-select
+                v-model="queryParams.status"
+                placeholder="用户状态"
+                clearable
+                style="width: 240px"
+            >
+              <el-option
+                  v-for="dict in sys_normal_disable"
+                  :key="dict.value"
+                  :label="dict.label"
+                  :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="创建时间" style="width: 308px;">
+            <el-date-picker
+                v-model="dateRange"
+                value-format="YYYY-MM-DD"
+                type="daterange"
+                range-separator="-"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+            ></el-date-picker>
+          </el-form-item>
+          <el-form-item>
+            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+          </el-form-item>
+        </el-form>
+
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button
+                type="primary"
+                plain
+                icon="Plus"
+                @click="handleAdd"
+                v-hasPermi="['system:user:add']"
+            >新增
+            </el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button
+                type="success"
+                plain
+                icon="Edit"
+                :disabled="single"
+                @click="handleUpdate"
+                v-hasPermi="['system:user:edit']"
+            >修改
+            </el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button
+                type="danger"
+                plain
+                icon="Delete"
+                :disabled="multiple"
+                @click="handleDelete"
+                v-hasPermi="['system:user:remove']"
+            >删除
+            </el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button
+                type="info"
+                plain
+                icon="Upload"
+                @click="handleImport"
+                v-hasPermi="['system:user:import']"
+            >导入
+            </el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button
+                type="warning"
+                plain
+                icon="Download"
+                @click="handleExport"
+                v-hasPermi="['system:user:export']"
+            >导出
+            </el-button>
+          </el-col>
+          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
+        </el-row>
+
+        <el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
+          <el-table-column type="selection" width="50" align="center"/>
+          <el-table-column label="用户编号" align="center" key="userId" prop="userId" v-if="columns[0].visible"/>
+          <el-table-column label="用户名称" align="center" key="userName" prop="userName" v-if="columns[1].visible"
+                           :show-overflow-tooltip="true"/>
+          <el-table-column label="用户昵称" align="center" key="nickName" prop="nickName" v-if="columns[2].visible"
+                           :show-overflow-tooltip="true"/>
+          <el-table-column label="部门" align="center" key="deptName" prop="dept.deptName" v-if="columns[3].visible"
+                           :show-overflow-tooltip="true"/>
+          <el-table-column label="手机号码" align="center" key="phonenumber" prop="phonenumber"
+                           v-if="columns[4].visible" width="120"/>
+          <el-table-column label="状态" align="center" key="status" v-if="columns[5].visible">
+            <template #default="scope">
+              <el-switch
+                  v-model="scope.row.status"
+                  active-value="0"
+                  inactive-value="1"
+                  @change="handleStatusChange(scope.row)"
+              ></el-switch>
             </template>
-         </el-upload>
-         <template #footer>
-            <div class="dialog-footer">
-               <el-button type="primary" @click="submitFileForm">确 定</el-button>
-               <el-button @click="upload.open = false">取 消</el-button>
+          </el-table-column>
+          <el-table-column label="创建时间" align="center" prop="createTime" v-if="columns[6].visible" width="160">
+            <template #default="scope">
+              <span>{{ parseTime(scope.row.createTime) }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="操作" align="center" width="150" class-name="small-padding fixed-width">
+            <template #default="scope">
+              <el-tooltip content="修改" placement="top" v-if="scope.row.userId !== 1">
+                <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)"
+                           v-hasPermi="['system:user:edit']"></el-button>
+              </el-tooltip>
+              <el-tooltip content="删除" placement="top" v-if="scope.row.userId !== 1">
+                <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"
+                           v-hasPermi="['system:user:remove']"></el-button>
+              </el-tooltip>
+              <el-tooltip content="重置密码" placement="top" v-if="scope.row.userId !== 1">
+                <el-button link type="primary" icon="Key" @click="handleResetPwd(scope.row)"
+                           v-hasPermi="['system:user:resetPwd']"></el-button>
+              </el-tooltip>
+              <el-tooltip content="分配角色" placement="top" v-if="scope.row.userId !== 1">
+                <el-button link type="primary" icon="CircleCheck" @click="handleAuthRole(scope.row)"
+                           v-hasPermi="['system:user:edit']"></el-button>
+              </el-tooltip>
+            </template>
+          </el-table-column>
+        </el-table>
+        <pagination
+            v-show="total > 0"
+            :total="total"
+            v-model:page="queryParams.pageNum"
+            v-model:limit="queryParams.pageSize"
+            @pagination="getList"
+        />
+      </el-col>
+    </el-row>
+
+    <!-- 添加或修改用户配置对话框 -->
+    <el-dialog :title="title" v-model="open" width="600px" append-to-body>
+      <el-form :model="form" :rules="rules" ref="userRef" label-width="80px">
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="用户昵称" prop="nickName">
+              <el-input v-model="form.nickName" placeholder="请输入用户昵称" maxlength="30"/>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="归属部门" prop="deptId">
+              <el-tree-select
+                  v-model="form.deptId"
+                  :data="deptOptions"
+                  :props="{ value: 'id', label: 'label', children: 'children' }"
+                  value-key="id"
+                  placeholder="请选择归属部门"
+                  check-strictly
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="手机号码" prop="phonenumber">
+              <el-input v-model="form.phonenumber" placeholder="请输入手机号码" maxlength="11"/>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="邮箱" prop="email">
+              <el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50"/>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="12">
+            <el-form-item v-if="form.userId == undefined" label="用户名称" prop="userName">
+              <el-input v-model="form.userName" placeholder="请输入用户名称" maxlength="30"/>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item v-if="form.userId == undefined" label="用户密码" prop="password">
+              <el-input v-model="form.password" placeholder="请输入用户密码" type="password" maxlength="20"
+                        show-password/>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="用户性别">
+              <el-select v-model="form.sex" placeholder="请选择">
+                <el-option
+                    v-for="dict in sys_user_sex"
+                    :key="dict.value"
+                    :label="dict.label"
+                    :value="dict.value"
+                ></el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="状态">
+              <el-radio-group v-model="form.status">
+                <el-radio
+                    v-for="dict in sys_normal_disable"
+                    :key="dict.value"
+                    :value="dict.value"
+                >{{ dict.label }}
+                </el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="岗位">
+              <el-select v-model="form.postIds" multiple placeholder="请选择">
+                <el-option
+                    v-for="item in postOptions"
+                    :key="item.postId"
+                    :label="item.postName"
+                    :value="item.postId"
+                    :disabled="item.status == 1"
+                ></el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="角色">
+              <el-select v-model="form.roleIds" multiple placeholder="请选择">
+                <el-option
+                    v-for="item in roleOptions"
+                    :key="item.roleId"
+                    :label="item.roleName"
+                    :value="item.roleId"
+                    :disabled="item.status == 1"
+                ></el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="备注">
+              <el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 用户导入对话框 -->
+    <el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body>
+      <el-upload
+          ref="uploadRef"
+          :limit="1"
+          accept=".xlsx, .xls"
+          :headers="upload.headers"
+          :action="upload.url + '?updateSupport=' + upload.updateSupport"
+          :disabled="upload.isUploading"
+          :on-progress="handleFileUploadProgress"
+          :on-success="handleFileSuccess"
+          :auto-upload="false"
+          drag
+      >
+        <el-icon class="el-icon--upload">
+          <upload-filled/>
+        </el-icon>
+        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
+        <template #tip>
+          <div class="el-upload__tip text-center">
+            <div class="el-upload__tip">
+              <el-checkbox v-model="upload.updateSupport"/>
+              是否更新已经存在的用户数据
             </div>
-         </template>
-      </el-dialog>
-   </div>
+            <span>仅允许导入xls、xlsx格式文件。</span>
+            <el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;"
+                     @click="importTemplate">下载模板
+            </el-link>
+          </div>
+        </template>
+      </el-upload>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitFileForm">确 定</el-button>
+          <el-button @click="upload.open = false">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
 </template>
 
 <script setup name="User">
-import { getToken } from "@/utils/auth";
-import { changeUserStatus, listUser, resetUserPwd, delUser, getUser, updateUser, addUser, deptTreeSelect } from "@/api/system/user";
+import {getToken} from "@/utils/auth";
+import {
+  addUser,
+  changeUserStatus,
+  delUser,
+  deptTreeSelect,
+  getUser,
+  listUser,
+  resetUserPwd,
+  updateUser
+} from "@/api/system/user";
 
 const router = useRouter();
-const { proxy } = getCurrentInstance();
-const { sys_normal_disable, sys_user_sex } = proxy.useDict("sys_normal_disable", "sys_user_sex");
+const {proxy} = getCurrentInstance();
+const {sys_normal_disable, sys_user_sex} = proxy.useDict("sys_normal_disable", "sys_user_sex");
 
 const userList = ref([]);
 const open = ref(false);
@@ -363,19 +392,19 @@ const upload = reactive({
   // 是否更新已经存在的用户数据
   updateSupport: 0,
   // 设置上传的请求头部
-  headers: { Authorization: "Bearer " + getToken() },
+  headers: {Authorization: "Bearer " + getToken()},
   // 上传的地址
   url: import.meta.env.VITE_APP_BASE_API + "/system/user/importData"
 });
 // 列显隐信息
 const columns = ref([
-  { key: 0, label: `用户编号`, visible: true },
-  { key: 1, label: `用户名称`, visible: true },
-  { key: 2, label: `用户昵称`, visible: true },
-  { key: 3, label: `部门`, visible: true },
-  { key: 4, label: `手机号码`, visible: true },
-  { key: 5, label: `状态`, visible: true },
-  { key: 6, label: `创建时间`, visible: true }
+  {key: 0, label: `用户编号`, visible: true},
+  {key: 1, label: `用户名称`, visible: true},
+  {key: 2, label: `用户昵称`, visible: true},
+  {key: 3, label: `部门`, visible: true},
+  {key: 4, label: `手机号码`, visible: true},
+  {key: 5, label: `状态`, visible: true},
+  {key: 6, label: `创建时间`, visible: true}
 ]);
 
 const data = reactive({
@@ -389,15 +418,57 @@ const data = reactive({
     deptId: undefined
   },
   rules: {
-    userName: [{ required: true, message: "用户名称不能为空", trigger: "blur" }, { min: 2, max: 20, message: "用户名称长度必须介于 2 和 20 之间", trigger: "blur" }],
-    nickName: [{ required: true, message: "用户昵称不能为空", trigger: "blur" }],
-    password: [{ required: true, message: "用户密码不能为空", trigger: "blur" }, { min: 5, max: 20, message: "用户密码长度必须介于 5 和 20 之间", trigger: "blur" }, { pattern: /^[^<>"'|\\]+$/, message: "不能包含非法字符:< > \" ' \\\ |", trigger: "blur" }],
-    email: [{ type: "email", message: "请输入正确的邮箱地址", trigger: ["blur", "change"] }],
-    phonenumber: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请输入正确的手机号码", trigger: "blur" }]
+    userName: [{required: true, message: "用户名称不能为空", trigger: "blur"}, {
+      min: 2,
+      max: 20,
+      message: "用户名称长度必须介于 2 和 20 之间",
+      trigger: "blur"
+    }],
+    nickName: [{required: true, message: "用户昵称不能为空", trigger: "blur"}],
+    password: [
+      {required: true, message: "用户密码不能为空", trigger: "blur"},
+      {min: 8, max: 20, message: "用户密码长度必须介于 8 和 20 之间", trigger: "blur"},
+      {pattern: /^[^<>"'|\\]+$/, message: "不能包含非法字符:< > \" ' \\\ |", trigger: "blur"},
+      {validator: validatePasswordComplexity, trigger: "blur"}
+    ],
+    email: [{type: "email", message: "请输入正确的邮箱地址", trigger: ["blur", "change"]}],
+    phonenumber: [{pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请输入正确的手机号码", trigger: "blur"}]
   }
 });
 
-const { queryParams, form, rules } = toRefs(data);
+const {queryParams, form, rules} = toRefs(data);
+
+/** 密码复杂度验证 */
+function validatePasswordComplexity(rule, value, callback) {
+  if (!value) {
+    return callback();
+  }
+
+  let complexCount = 0;
+  // 包含小写字母
+  if (/[a-z]/.test(value)) {
+    complexCount++;
+  }
+  // 包含大写字母
+  if (/[A-Z]/.test(value)) {
+    complexCount++;
+  }
+  // 包含数字
+  if (/\d/.test(value)) {
+    complexCount++;
+  }
+  // 包含特殊字符
+  if (/[^a-zA-Z\d]/.test(value)) {
+    complexCount++;
+  }
+
+  if (complexCount < 3) {
+    callback(new Error("密码必须包含大写字母、小写字母、数字、特殊字符中的至少3种"));
+  } else {
+    callback();
+  }
+}
+
 
 /** 通过条件过滤节点  */
 const filterNode = (value, data) => {
@@ -456,14 +527,15 @@ function handleDelete(row) {
   }).then(() => {
     getList();
     proxy.$modal.msgSuccess("删除成功");
-  }).catch(() => {});
+  }).catch(() => {
+  });
 };
 
 /** 导出按钮操作 */
 function handleExport() {
-  proxy.download("system/user/export", { 
+  proxy.download("system/user/export", {
     ...queryParams.value,
-  },`user_${new Date().getTime()}.xlsx`);
+  }, `user_${new Date().getTime()}.xlsx`);
 };
 
 /** 用户状态修改  */
@@ -511,11 +583,12 @@ function handleResetPwd(row) {
         return "不能包含非法字符:< > \" ' \\\ |"
       }
     },
-  }).then(({ value }) => {
+  }).then(({value}) => {
     resetUserPwd(row.userId, value).then(response => {
       proxy.$modal.msgSuccess("修改成功,新密码是:" + value);
     });
-  }).catch(() => {});
+  }).catch(() => {
+  });
 };
 
 /** 选择条数  */
@@ -533,8 +606,7 @@ function handleImport() {
 
 /** 下载模板操作 */
 function importTemplate() {
-  proxy.download("system/user/importTemplate", {
-  }, `user_template_${new Date().getTime()}.xlsx`);
+  proxy.download("system/user/importTemplate", {}, `user_template_${new Date().getTime()}.xlsx`);
 };
 
 /**文件上传中处理 */
@@ -547,7 +619,7 @@ const handleFileSuccess = (response, file, fileList) => {
   upload.open = false;
   upload.isUploading = false;
   proxy.$refs["uploadRef"].handleRemove(file);
-  proxy.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "导入结果", { dangerouslyUseHTMLString: true });
+  proxy.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "导入结果", {dangerouslyUseHTMLString: true});
   getList();
 };
 

+ 30 - 18
ruoyi-ui/src/views/system/user/profile/resetPwd.vue

@@ -1,25 +1,26 @@
 <template>
-   <el-form ref="pwdRef" :model="user" :rules="rules" label-width="80px">
-      <el-form-item label="旧密码" prop="oldPassword">
-         <el-input v-model="user.oldPassword" placeholder="请输入旧密码" type="password" show-password />
-      </el-form-item>
-      <el-form-item label="新密码" prop="newPassword">
-         <el-input v-model="user.newPassword" placeholder="请输入新密码" type="password" show-password />
-      </el-form-item>
-      <el-form-item label="确认密码" prop="confirmPassword">
-         <el-input v-model="user.confirmPassword" placeholder="请确认新密码" type="password" show-password/>
-      </el-form-item>
-      <el-form-item>
+  <el-form ref="pwdRef" :model="user" :rules="rules" label-width="80px">
+    <el-form-item label="旧密码" prop="oldPassword">
+      <el-input v-model="user.oldPassword" placeholder="请输入旧密码" type="password" show-password/>
+    </el-form-item>
+    <el-form-item label="新密码" prop="newPassword">
+      <el-input v-model="user.newPassword" placeholder="请输入新密码" type="password" show-password/>
+    </el-form-item>
+    <el-form-item label="确认密码" prop="confirmPassword">
+      <el-input v-model="user.confirmPassword" placeholder="请确认新密码" type="password" show-password/>
+    </el-form-item>
+    <el-form-item>
       <el-button type="primary" @click="submit">保存</el-button>
       <el-button type="danger" @click="close">关闭</el-button>
-      </el-form-item>
-   </el-form>
+    </el-form-item>
+  </el-form>
 </template>
 
 <script setup>
-import { updateUserPwd } from "@/api/system/user";
+import {updateUserPwd} from "@/api/system/user";
 
-const { proxy } = getCurrentInstance();
+const {proxy} = getCurrentInstance();
+const emit = defineEmits(["close","success"]);
 
 const user = reactive({
   oldPassword: undefined,
@@ -36,9 +37,18 @@ const equalToPassword = (rule, value, callback) => {
 };
 
 const rules = ref({
-  oldPassword: [{ required: true, message: "旧密码不能为空", trigger: "blur" }],
-  newPassword: [{ required: true, message: "新密码不能为空", trigger: "blur" }, { min: 6, max: 20, message: "长度在 6 到 20 个字符", trigger: "blur" }, { pattern: /^[^<>"'|\\]+$/, message: "不能包含非法字符:< > \" ' \\\ |", trigger: "blur" }],
-  confirmPassword: [{ required: true, message: "确认密码不能为空", trigger: "blur" }, { required: true, validator: equalToPassword, trigger: "blur" }]
+  oldPassword: [{required: true, message: "旧密码不能为空", trigger: "blur"}],
+  newPassword: [{required: true, message: "新密码不能为空", trigger: "blur"}, {
+    min: 6,
+    max: 20,
+    message: "长度在 6 到 20 个字符",
+    trigger: "blur"
+  }, {pattern: /^[^<>"'|\\]+$/, message: "不能包含非法字符:< > \" ' \\\ |", trigger: "blur"}],
+  confirmPassword: [{required: true, message: "确认密码不能为空", trigger: "blur"}, {
+    required: true,
+    validator: equalToPassword,
+    trigger: "blur"
+  }]
 });
 
 /** 提交按钮 */
@@ -47,6 +57,7 @@ function submit() {
     if (valid) {
       updateUserPwd(user.oldPassword, user.newPassword).then(response => {
         proxy.$modal.msgSuccess("修改成功");
+        emit("success")
       });
     }
   });
@@ -55,5 +66,6 @@ function submit() {
 /** 关闭按钮 */
 function close() {
   proxy.$tab.closePage();
+  emit("close")
 };
 </script>