ソースを参照

租户激活码功能

侯茂昌 1 年間 前
コミット
c8765d6edc
21 ファイル変更1022 行追加16 行削除
  1. 65 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysActivationLogController.java
  2. 4 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java
  3. 17 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysTenantController.java
  4. 2 2
      ruoyi-admin/src/main/resources/application-druid.yml
  5. 2 2
      ruoyi-admin/src/main/resources/application.yml
  6. 7 0
      ruoyi-common/pom.xml
  7. 11 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysTenant.java
  8. 22 0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/DateUtils.java
  9. 43 0
      ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java
  10. 64 0
      ruoyi-system/src/main/java/com/ruoyi/system/domain/SysActivationCodeLog.java
  11. 50 0
      ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysActivationCodeLogMapper.java
  12. 50 0
      ruoyi-system/src/main/java/com/ruoyi/system/service/ISysActivationCodeLogService.java
  13. 11 0
      ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTenantService.java
  14. 70 0
      ruoyi-system/src/main/java/com/ruoyi/system/service/impl/ISysActivationCodeLogServiceImpl.java
  15. 151 4
      ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTenantServiceImpl.java
  16. 65 0
      ruoyi-system/src/main/resources/mapper/system/SysActivationCodeLogMapper.xml
  17. 3 2
      ruoyi-system/src/main/resources/mapper/system/SysTenantMapper.xml
  18. 26 0
      ruoyi-ui/src/api/monitor/activationcode.js
  19. 16 1
      ruoyi-ui/src/api/system/tenant.js
  20. 274 0
      ruoyi-ui/src/views/system/tenant/code/index.vue
  21. 69 5
      ruoyi-ui/src/views/system/tenant/index.vue

+ 65 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/SysActivationLogController.java

@@ -0,0 +1,65 @@
+package com.ruoyi.web.controller.monitor;
+
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.system.domain.SysActivationCodeLog;
+import com.ruoyi.system.service.ISysActivationCodeLogService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 操作日志记录
+ * 
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/monitor/activationlog")
+public class SysActivationLogController extends BaseController
+{
+    @Autowired
+    private ISysActivationCodeLogService iSysActivationCodeLogService;
+
+    @PreAuthorize("@ss.hasPermi('monitor:activationcodelog:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(SysActivationCodeLog activationCodeLog)
+    {
+        startPage();
+        List<SysActivationCodeLog> list = iSysActivationCodeLogService.selectActivationCodeLogList(activationCodeLog);
+        return getDataTable(list);
+    }
+
+    @Log(title = "操作日志", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('monitor:activationcodelog:export')")
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, SysActivationCodeLog activationCodeLog)
+    {
+        List<SysActivationCodeLog> list = iSysActivationCodeLogService.selectActivationCodeLogList(activationCodeLog);
+        ExcelUtil<SysActivationCodeLog> util = new ExcelUtil<>(SysActivationCodeLog.class);
+        util.exportExcel(response, list, "操作日志");
+    }
+
+    @Log(title = "操作日志", businessType = BusinessType.DELETE)
+    @PreAuthorize("@ss.hasPermi('monitor:activationcodelog:remove')")
+    @DeleteMapping("/{logIds}")
+    public AjaxResult remove(@PathVariable Long[] logIds)
+    {
+        return toAjax(iSysActivationCodeLogService.deleteActivationCodeLogById(logIds));
+    }
+
+    @Log(title = "操作日志", businessType = BusinessType.CLEAN)
+    @PreAuthorize("@ss.hasPermi('monitor:activationcodelog:remove')")
+    @DeleteMapping("/clean")
+    public AjaxResult clean()
+    {
+        iSysActivationCodeLogService.cleanActivationCodeLog();
+        return success();
+    }
+}

+ 4 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java

@@ -79,6 +79,10 @@ public class SysLoginController {
     public AjaxResult login(@RequestBody LoginBody loginBody) {
         //校验租户状态?生成token
         AjaxResult ajax = AjaxResult.success();
+        String  checkTenantExpirationTimeMsg= loginService.checkTenantExpirationTime(loginBody.getUsername());
+        if(!checkTenantExpirationTimeMsg.isEmpty()){
+            return AjaxResult.error(checkTenantExpirationTimeMsg);
+        }
         // 生成令牌
         String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),
                 loginBody.getUuid());

+ 17 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysTenantController.java

@@ -112,4 +112,21 @@ public class SysTenantController extends BaseController
     public AjaxResult initTenantMenuData(@PathVariable Long tenantId){
         return sysTenantService.initTenantMenuData(tenantId);
     }
+
+    /**
+     * 生成激活码方法
+     */
+    @GetMapping("/crateTenantCode/{tenantId}/{tenantExpirationTime}")
+    public AjaxResult crateTenantCode(@PathVariable String tenantId,@PathVariable String tenantExpirationTime) throws Exception {
+        return sysTenantService.crateTenantCode(tenantId,tenantExpirationTime);
+    }
+
+    /**
+     * 激活租户
+     */
+    @GetMapping("/activationOperation/{tenantId}/{activationCode}")
+    public AjaxResult activationOperation(@PathVariable String tenantId,@PathVariable String activationCode) throws Exception {
+        return sysTenantService.activationOperation(tenantId,activationCode);
+    }
+
 }

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

@@ -6,9 +6,9 @@ spring:
         druid:
             # 主库数据源
             master:
-                url: jdbc:mysql://localhost:3306/ry-vue-call?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                url: jdbc:mysql://192.168.110.15:3306/ry-vue-call?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
                 username: root
-                password: 123456
+                password: zkqy8888
             # 从库数据源
             slave:
                 # 从数据源开关/默认关闭

+ 2 - 2
ruoyi-admin/src/main/resources/application.yml

@@ -70,13 +70,13 @@ spring:
   # redis 配置
   redis:
     # 地址
-    host: localhost
+    host: 192.168.110.15
     # 端口,默认为6379
     port: 6379
     # 数据库索引
     database: 0
     # 密码
-    password: 123456
+    #password: 123456
     # 连接超时时间
     timeout: 10s
     lettuce:

+ 7 - 0
ruoyi-common/pom.xml

@@ -126,11 +126,18 @@
             <artifactId>javax.servlet-api</artifactId>
         </dependency>
 
+        <!-- lombok-->
         <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
         </dependency>
 
+        <!-- 糊涂工具包-->
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>5.8.16</version>
+        </dependency>
     </dependencies>
 
 </project>

+ 11 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysTenant.java

@@ -49,6 +49,17 @@ public class SysTenant extends BaseEntity
 
     private DataSource dataSource;
 
+    /** 租户的到期时间 */
+    private String tenantExpirationTime;
+
+    public String getTenantExpirationTime() {
+        return tenantExpirationTime;
+    }
+
+    public void setTenantExpirationTime(String tenantExpirationTime) {
+        this.tenantExpirationTime = tenantExpirationTime;
+    }
+
     public Long getTenantId() {
         return tenantId;
     }

+ 22 - 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/DateUtils.java

@@ -8,6 +8,7 @@ import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.Date;
 import org.apache.commons.lang3.time.DateFormatUtils;
 
@@ -188,4 +189,25 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
         ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());
         return Date.from(zdt.toInstant());
     }
+
+    /**
+     * 日期类型转换 String 转 LocalDateTime
+     */
+
+    public static LocalDateTime toLocalDateTime(String dateTime,String df)
+    {
+        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(df);
+        LocalDateTime localDateTime= LocalDateTime.parse(dateTime, dateTimeFormatter);
+        return localDateTime;
+    }
+
+    /**
+     * 日期类型转换 LocalDateTime 转 String
+     */
+    public static String toLocalDateTimeStr(LocalDateTime dateTime)
+    {
+        DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+        String dateStr = dateTime.format(fmt);
+        return dateStr;
+    }
 }

+ 43 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java

@@ -1,6 +1,12 @@
 package com.ruoyi.framework.web.service;
 
 import javax.annotation.Resource;
+
+import cn.hutool.core.util.CharsetUtil;
+import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
+import cn.hutool.crypto.symmetric.SymmetricCrypto;
+import com.ruoyi.common.core.domain.entity.SysTenant;
+import com.ruoyi.system.service.impl.SysTenantServiceImpl;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.BadCredentialsException;
@@ -29,6 +35,9 @@ import com.ruoyi.framework.security.context.AuthenticationContextHolder;
 import com.ruoyi.system.service.ISysConfigService;
 import com.ruoyi.system.service.ISysUserService;
 
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+
 /**
  * 登录校验方法
  * 
@@ -52,6 +61,9 @@ public class SysLoginService
     @Autowired
     private ISysConfigService configService;
 
+    @Autowired
+    private SysTenantServiceImpl sysTenantService;
+
     /**
      * 登录验证
      * 
@@ -178,4 +190,35 @@ public class SysLoginService
         sysUser.setLoginDate(DateUtils.getNowDate());
         userService.updateUserProfile(sysUser);
     }
+
+    public String checkTenantExpirationTime(String username) {
+        SysUser user = userService.selectUserByUserName(username);
+        //拿到当前登录用户
+        System.out.println(user);
+        if(!user.getUserName().equals("admin")){
+            //根据租户id查询租户信息
+            SysTenant sysTenant = sysTenantService.selectSysTenantByTenantId(user.getTenantId());
+            if(sysTenant!=null){
+                System.out.println("dfsdfasdfasd");
+            }
+            if(sysTenant.getTenantExpirationTime()==null||sysTenant.getTenantExpirationTime().isEmpty()){
+                return "该用户租户信息并未激活";
+            }
+            SymmetricCrypto symmetricCrypto = new SymmetricCrypto(SymmetricAlgorithm.DES, "sgEsnN6QWq8W7j5H01020304".getBytes());
+            //当前时间
+            LocalDateTime now=LocalDateTime.now();
+            long nowSecond = now.toEpochSecond(ZoneOffset.ofHours(8));
+            //到期时间
+            String LastActiveTimeStr = symmetricCrypto.decryptStr(sysTenant.getTenantExpirationTime(), CharsetUtil.CHARSET_UTF_8);
+            LocalDateTime LastActiveDateTime = DateUtils.toLocalDateTime(LastActiveTimeStr, "yyyy-MM-dd HH:mm:ss");
+            long expirationTimeSecond = LastActiveDateTime.toEpochSecond(ZoneOffset.ofHours(8));
+            //计算时间戳
+            long difference=expirationTimeSecond-nowSecond; //相差的时间数 后边减前边
+            //如果是负数证明已经过期了
+            if(difference<0){
+                return "该用户租户信息已过期";
+            }
+        }
+        return "";
+    }
 }

+ 64 - 0
ruoyi-system/src/main/java/com/ruoyi/system/domain/SysActivationCodeLog.java

@@ -0,0 +1,64 @@
+package com.ruoyi.system.domain;
+
+import com.ruoyi.common.core.domain.BaseEntity;
+
+import java.util.Date;
+
+/**
+ * @author hmc
+ * @date 2023-10-24 13:19
+ * @Description:
+ */
+public class SysActivationCodeLog extends BaseEntity {
+
+    /** 日志id */
+    private int logId;
+    /** 激活码生成时间**/
+    private Date generationTime;
+    /** 操作人**/
+    private String operator;
+    /** ip地址**/
+    private String ipAddress;
+    /**备注**/
+    private String note;
+
+    public int getLogId() {
+        return logId;
+    }
+
+    public void setLogId(int logId) {
+        this.logId = logId;
+    }
+
+    public Date getGenerationTime() {
+        return generationTime;
+    }
+
+    public void setGenerationTime(Date generationTime) {
+        this.generationTime = generationTime;
+    }
+
+    public String getOperator() {
+        return operator;
+    }
+
+    public void setOperator(String operator) {
+        this.operator = operator;
+    }
+
+    public String getIpAddress() {
+        return ipAddress;
+    }
+
+    public void setIpAddress(String ipAddress) {
+        this.ipAddress = ipAddress;
+    }
+
+    public String getNote() {
+        return note;
+    }
+
+    public void setNote(String note) {
+        this.note = note;
+    }
+}

+ 50 - 0
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysActivationCodeLogMapper.java

@@ -0,0 +1,50 @@
+package com.ruoyi.system.mapper;
+
+import com.ruoyi.system.domain.SysActivationCodeLog;
+import com.ruoyi.system.domain.SysOperLog;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * @author hmc
+ * @date 2023-10-24 13:27
+ * @Description:
+ */
+@Mapper
+public interface SysActivationCodeLogMapper {
+
+    /**
+     * 新增数据源
+     */
+    int insertDataSource(SysActivationCodeLog sysActivationCodeLog);
+
+    /**
+     * 查询系统操作日志集合
+     *
+     * @param sysActivationCodeLog 操作日志对象
+     * @return 操作日志集合
+     */
+    public List<SysActivationCodeLog> selectActivationCodeLogList(SysActivationCodeLog sysActivationCodeLog);
+
+    /**
+     * 批量删除系统操作日志
+     *
+     * @param logIds 需要删除的操作日志ID
+     * @return 结果
+     */
+    public int deleteActivationCodeLogByIds(Long[] logIds);
+
+    /**
+     * 查询操作日志详细
+     *
+     * @param logId 操作ID
+     * @return 操作日志对象
+     */
+    public SysOperLog selectActivationCodeLogById(Long logId);
+
+    /**
+     * 清空操作日志
+     */
+    public void cleanActivationCodeLog();
+}

+ 50 - 0
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysActivationCodeLogService.java

@@ -0,0 +1,50 @@
+package com.ruoyi.system.service;
+
+import com.ruoyi.system.domain.SysActivationCodeLog;
+import com.ruoyi.system.domain.SysOperLog;
+
+import java.util.List;
+
+/**
+ * 操作日志 服务层
+ * 
+ * @author ruoyi
+ */
+public interface ISysActivationCodeLogService
+{
+    /**
+     * 新增激活码操作日志
+     * 
+     * @param sysActivationCodeLog 操作日志对象
+     */
+    public void insertActivationCodeLog(SysActivationCodeLog sysActivationCodeLog);
+
+    /**
+     * 查询系统操作日志集合
+     * 
+     * @param sysActivationCodeLog 操作日志对象
+     * @return 操作日志集合
+     */
+    public List<SysActivationCodeLog> selectActivationCodeLogList(SysActivationCodeLog sysActivationCodeLog);
+
+    /**
+     * 批量删除系统操作日志
+     * 
+     * @param logIds 需要删除的操作日志ID
+     * @return 结果
+     */
+    public int deleteActivationCodeLogById(Long[] logIds);
+
+    /**
+     * 查询操作日志详细
+     * 
+     * @param logId 操作ID
+     * @return 操作日志对象
+     */
+    public SysOperLog selectActivationCodeLogById(Long logId);
+
+    /**
+     * 清空操作日志
+     */
+    public void cleanActivationCodeLog();
+}

+ 11 - 0
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTenantService.java

@@ -65,4 +65,15 @@ public interface ISysTenantService
      * 初始化租户菜单关联表数据(租户默认菜单)
      */
     AjaxResult initTenantMenuData(Long tenantId);
+
+
+    /**
+     * 创建租户激活码
+     */
+    AjaxResult crateTenantCode( String tenantId,String tenantExpirationTime) throws Exception;
+
+    /**
+     * 激活租户
+     */
+    AjaxResult activationOperation( String tenantId,String activationCode) throws Exception;
 }

+ 70 - 0
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/ISysActivationCodeLogServiceImpl.java

@@ -0,0 +1,70 @@
+package com.ruoyi.system.service.impl;
+
+import com.ruoyi.system.domain.SysActivationCodeLog;
+import com.ruoyi.system.domain.SysOperLog;
+import com.ruoyi.system.mapper.SysActivationCodeLogMapper;
+import com.ruoyi.system.service.ISysActivationCodeLogService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * @author lucky
+ * @date 2023-10-26 13:36
+ * @Description:
+ */
+
+@Service
+public class ISysActivationCodeLogServiceImpl implements ISysActivationCodeLogService {
+
+
+    @Autowired
+    SysActivationCodeLogMapper sysActivationCodeLogMapper;
+
+    /**
+     * 新增操作日志
+     * @param sysActivationCodeLog 操作日志对象
+     */
+    @Override
+    public void insertActivationCodeLog(SysActivationCodeLog sysActivationCodeLog) {
+        sysActivationCodeLogMapper.insertDataSource(sysActivationCodeLog);
+    }
+
+    /**
+     * 查询激活码操作日志集合
+     * @param sysActivationCodeLog 操作日志对象
+     */
+    @Override
+    public List<SysActivationCodeLog> selectActivationCodeLogList(SysActivationCodeLog sysActivationCodeLog) {
+       return sysActivationCodeLogMapper.selectActivationCodeLogList(sysActivationCodeLog);
+    }
+
+    /**
+     * 批量删除激活码操作日志
+     * @param logIds 需要删除的操作日志ID
+     * @return 结果
+     */
+    @Override
+    public int deleteActivationCodeLogById(Long[] logIds) {
+        return sysActivationCodeLogMapper.deleteActivationCodeLogByIds(logIds);
+    }
+
+    /**
+     * 查询激活码操作日志详细
+     * @param logId 操作ID
+     * @return 操作日志对象
+     */
+    @Override
+    public SysOperLog selectActivationCodeLogById(Long logId) {
+        return sysActivationCodeLogMapper.selectActivationCodeLogById(logId);
+    }
+
+    /**
+     * 清空激活码操作日志
+     */
+    @Override
+    public void cleanActivationCodeLog() {
+        sysActivationCodeLogMapper.cleanActivationCodeLog();
+    }
+}

+ 151 - 4
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTenantServiceImpl.java

@@ -5,21 +5,32 @@ import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Paths;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.time.temporal.ChronoUnit;
 import java.util.*;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
+import cn.hutool.core.date.LocalDateTimeUtil;
+import cn.hutool.core.util.CharsetUtil;
+import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
+import cn.hutool.crypto.symmetric.SymmetricCrypto;
 import com.fasterxml.jackson.databind.ObjectMapper;
 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.utils.DateUtils;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.ip.IpUtils;
+import com.ruoyi.system.domain.SysActivationCodeLog;
 import com.ruoyi.system.domain.SysTenantMenu;
-import com.ruoyi.system.mapper.SysMenuMapper;
-import com.ruoyi.system.mapper.SysTenantMenuMapper;
-import com.ruoyi.system.mapper.SysUserMapper;
+import com.ruoyi.system.mapper.*;
 import com.ruoyi.system.service.ISysUserService;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.StringRedisTemplate;
 import org.springframework.stereotype.Service;
-import com.ruoyi.system.mapper.SysTenantMapper;
 import com.ruoyi.common.core.domain.entity.SysTenant;
 import com.ruoyi.system.service.ISysTenantService;
 import org.springframework.transaction.annotation.Transactional;
@@ -48,6 +59,12 @@ public class SysTenantServiceImpl implements ISysTenantService
     @Autowired
     private SysUserMapper sysUserMapper;
 
+    @Autowired
+    private StringRedisTemplate stringRedisTemplate;
+
+    @Autowired
+    private SysActivationCodeLogMapper sysActivationCodeLogMapper;
+
     /**
      * 查询租户信息
      * 
@@ -184,5 +201,135 @@ public class SysTenantServiceImpl implements ISysTenantService
         }
     }
 
+    /**
+     * 生成激活码
+     * @param tenantId
+     * @param tenantExpirationTime
+     * @return
+     */
+    @Override
+    public AjaxResult crateTenantCode(String tenantId,String tenantExpirationTime) throws Exception {
+        //加密器
+        SymmetricCrypto des = new SymmetricCrypto(SymmetricAlgorithm.DES, "sgEsnN6QWq8W7j5H01020304".getBytes());
+        //激活码生成时间
+        LocalDateTime now = LocalDateTime.now();
+        String nowStr = DateUtils.toLocalDateTimeStr(now);
+        //激活码有效时间默认为24小时
+        LocalDateTime offset = LocalDateTimeUtil.offset(now, 1, ChronoUnit.DAYS);
+        String offsetStr = DateUtils.toLocalDateTimeStr(offset);
+        //激活码生成时间+租户id+激活码有效期+激活多长时间
+        String dataStr=nowStr+"_"+tenantId+"_"+offsetStr+"_"+tenantExpirationTime;
+        //加密信息
+        String encryptHex = des.encryptHex(dataStr);
+        //生成激活码操作
+        activationCodeLog("生成激活码");
+        return AjaxResult.success(encryptHex);
+    }
+
+    /**
+     * 激活码激活操作
+     * @param tenantId
+     * @param activationCode
+     * @return
+     * @throws Exception
+     */
+    @Override
+    public AjaxResult activationOperation(String tenantId, String activationCode) throws Exception {
+        String activeCode="active:code:"+activationCode;//魔法值后期抽出来
+        String strCode = stringRedisTemplate.opsForValue().get(activationCode);
+        if(StringUtils.isNotEmpty(strCode)){
+            return AjaxResult.error("当前激活码已经被使用过了不能重复使用");
+        }
+        //激活码生成时间+租户id+激活码有效期+激活多长时间
+        SymmetricCrypto symmetricCrypto = new SymmetricCrypto(SymmetricAlgorithm.DES, "sgEsnN6QWq8W7j5H01020304".getBytes());
+        String decryptStr = symmetricCrypto.decryptStr(activationCode, CharsetUtil.CHARSET_UTF_8);
+        String[] contentString = decryptStr.split("_");
+        //判断激活码是否失效
+        String expirationDateStr=contentString[2];
+        LocalDateTime expirationDate = DateUtils.toLocalDateTime(expirationDateStr,"yyyy-MM-dd HH:mm:ss");
+        long expirationDateSecond = expirationDate.toEpochSecond(ZoneOffset.ofHours(8));
+        long nowSecond = LocalDateTime.now().toEpochSecond(ZoneOffset.ofHours(8));
+        if(expirationDateSecond-nowSecond<0){
+            return AjaxResult.error("此激活码已经过期,不能在继续使用");
+        }
+        if(!tenantId.equals(contentString[1])){
+            return AjaxResult.error("当前激活码不能在当前租户使用");
+        }
+        //先查询、这个用户有没有被激活过
+        SysTenant sysTenant = sysTenantMapper.selectSysTenantByTenantId(Long.valueOf(tenantId));
+        SysTenant sysTenantOne=new SysTenant();
+        sysTenantOne.setTenantId(Long.valueOf(tenantId));
+        //判断有没有有被激活过、有时间代表激活过
+        if(StringUtils.isNotEmpty(sysTenant.getTenantExpirationTime())){
+            //解密
+            String LastActiveTime = symmetricCrypto.decryptStr(sysTenant.getTenantExpirationTime(), CharsetUtil.CHARSET_UTF_8);
+            LocalDateTime localDateTime = DateUtils.toLocalDateTime(LastActiveTime, "yyyy-MM-dd HH:mm:ss");
+            //当前日期时间戳
+            long  nowTimeSecond =LocalDateTime.now().toEpochSecond(ZoneOffset.ofHours(8));
+            //原来的过期时间
+            long  oldTimeSecond = localDateTime.toEpochSecond(ZoneOffset.ofHours(8));
+            //老的过期时间跟当前时间比较 如果小于0证明已经过期好久了
+            if(oldTimeSecond-nowTimeSecond<0){
+                //新续期的时间从当前时间进行续期操作
+                renewalTime(symmetricCrypto, contentString, sysTenantOne);
+            }else {
+                //老的时间快过期了还没过期(旧到期时间+新到期时间(续期操作))
+                LocalDateTime newActiveDateTime = LocalDateTimeUtil.offset(localDateTime, Long.parseLong(contentString[3]), ChronoUnit.DAYS);
+                //加密
+                String newActiveDateTimeStr= symmetricCrypto.encryptHex(DateUtils.toLocalDateTimeStr(newActiveDateTime));
+                //更新到期时间
+                sysTenantOne.setTenantExpirationTime(newActiveDateTimeStr);
+            }
+        }else {
+            //新续期的时间从当前时间进行续期操作
+            renewalTime(symmetricCrypto, contentString, sysTenantOne);
+        }
+        //更新租户的有效时间
+        sysTenantMapper.updateSysTenant(sysTenantOne);
+        //保存验证码操作日志
+        activationCodeLog("使用激活码");
+        //24小时之后就删除了我们保存的验证码信息
+        stringRedisTemplate.opsForValue().set(activeCode,activationCode,24, TimeUnit.HOURS);
+        return AjaxResult.success();
+    }
+
+    /**
+     * 从当前时间往后续期租户时间
+     * @param symmetricCrypto
+     * @param contentString
+     * @param sysTenantOne
+     */
+    private void renewalTime(SymmetricCrypto symmetricCrypto, String[] contentString, SysTenant sysTenantOne) {
+        //当前时间
+        LocalDateTime localDateTime = DateUtils.toLocalDateTime(DateUtils.toLocalDateTimeStr(LocalDateTime.now()), "yyyy-MM-dd HH:mm:ss");
+        //设置到期时间
+        LocalDateTime activeDateTime = LocalDateTimeUtil.offset(localDateTime, Long.parseLong(contentString[3]), ChronoUnit.DAYS);
+        //加密到期时间
+        String newActiveDateTimeStr= symmetricCrypto.encryptHex(DateUtils.toLocalDateTimeStr(activeDateTime));
+        sysTenantOne.setTenantExpirationTime(newActiveDateTimeStr);
+    }
+
+    /**
+     * 保存激活码操作日志
+     * @param msg
+     */
+    private void activationCodeLog(String msg) {
+        //保存生成激活码日志
+        SysUser user = SecurityUtils.getLoginUser().getUser();
+        SysActivationCodeLog activationCodeLog=new SysActivationCodeLog();
+        //生成时间
+        activationCodeLog.setGenerationTime(new Date());
+        //ip
+        activationCodeLog.setIpAddress(IpUtils.getIpAddr());
+        //操作人
+        activationCodeLog.setOperator(user.getUserName());
+        //备注
+        activationCodeLog.setNote(msg);
+        //插入操作日志
+        sysActivationCodeLogMapper.insertDataSource(activationCodeLog);
+    }
+
+
+
 
 }

+ 65 - 0
ruoyi-system/src/main/resources/mapper/system/SysActivationCodeLogMapper.xml

@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.system.mapper.SysActivationCodeLogMapper">
+    <!--自定义结果集封装-->
+    <resultMap type="SysActivationCodeLog" id="SysActivationCodeLogResult">
+        <id     property="logId"         column="log_id"        />
+        <result property="generationTime"          column="generation_time"          />
+        <result property="operator"   column="operator"  />
+        <result property="ipAddress"         column="ip_address"         />
+        <result property="note"  column="note" />
+    </resultMap>
+    <!--sql片段-->
+    <sql id="selectActivationCodeLogVo">
+        select log_id, generation_time, operator, ip_address, note
+        from sys_activation_code_logs
+    </sql>
+
+    <!--增加操作日志-->
+    <insert id="insertDataSource" useGeneratedKeys="true" keyProperty="logId">
+        insert into sys_activation_code_logs(log_id,generation_time,operator,ip_address,note)
+        values (#{logId},#{generationTime},#{operator},#{ipAddress},#{note})
+    </insert>
+
+    <!--查询此操作日志集合-->
+    <select id="selectActivationCodeLogList" resultMap="SysActivationCodeLogResult">
+        <include refid="selectActivationCodeLogVo"/>
+        <where>
+            <if test="ipAddress != null and ipAddress != ''">
+                AND ip_address = #{ipAddress}
+            </if>
+            <if test="operator != null and operator != ''">
+                AND operator like concat('%', #{operator}, '%')
+            </if>
+            <if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
+                AND generation_time &gt;= #{params.beginTime}
+            </if>
+            <if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
+                AND generation_time &lt;= #{params.endTime}
+            </if>
+        </where>
+        order by log_id desc
+    </select>
+
+    <!--批量删除-->
+    <delete id="deleteActivationCodeLogByIds" parameterType="Long">
+        delete from sys_activation_code_logs where log_id in
+        <foreach collection="array" item="logId" open="(" separator="," close=")">
+            #{logId}
+        </foreach>
+    </delete>
+
+    <!--清空日志-->
+    <update id="cleanActivationCodeLog">
+        truncate table sys_activation_code_logs
+    </update>
+
+    <!--查询激活码操作日志详情-->
+    <select id="selectActivationCodeLogById" resultMap="SysActivationCodeLogResult">
+        <include refid="selectActivationCodeLogVo"/>
+        where oper_id = #{logId}
+    </select>
+
+</mapper>

+ 3 - 2
ruoyi-system/src/main/resources/mapper/system/SysTenantMapper.xml

@@ -15,11 +15,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="createTime"    column="create_time"    />
         <result property="isDel"    column="is_del"    />
         <result property="datasourceId" column="datasource_id"/>
-
+        <result property="tenantExpirationTime" column="tenant_expiration_time"/>
     </resultMap>
 
     <sql id="selectSysTenantVo">
-        select tenant_id, tenant_name, tenant_code, owner, contact_info, address, create_by, create_time, is_del,datasource_id from sys_tenant where is_del = '0'
+        select tenant_id, tenant_name, tenant_code, owner, contact_info, address, create_by, create_time, is_del,datasource_id,tenant_expiration_time from sys_tenant where is_del = '0'
     </sql>
 
     <select id="selectSysTenantList" parameterType="SysTenant" resultMap="SysTenantResult">
@@ -75,6 +75,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="createBy != null and createBy != ''">create_by = #{createBy},</if>
             <if test="createTime != null">create_time = #{createTime},</if>
             <if test="datasourceId != null">datasource_id = #{datasourceId},</if>
+            <if test="tenantExpirationTime != null">tenant_expiration_time = #{tenantExpirationTime},</if>
         </trim>
         where tenant_id = #{tenantId}
     </update>

+ 26 - 0
ruoyi-ui/src/api/monitor/activationcode.js

@@ -0,0 +1,26 @@
+import request from '@/utils/request'
+
+// 查询操作日志列表
+export function list(query) {
+  return request({
+    url: '/monitor/activationlog/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 删除操作日志
+export function delActivationLog(logId) {
+  return request({
+    url: '/monitor/activationlog/' + logId,
+    method: 'delete'
+  })
+}
+
+// 清空操作日志
+export function cleanActivationLog() {
+  return request({
+    url: '/monitor/activationlog/clean',
+    method: 'delete'
+  })
+}

+ 16 - 1
ruoyi-ui/src/api/system/tenant.js

@@ -75,4 +75,19 @@ export function initTenantMenuData(tenantId) {
     url: `/system/tenant/initTenantMenuData/${tenantId}`,
     method: 'get'
   })
-}
+}
+
+// 生成激活码
+export function createTenantCode(data) {
+  return request({
+    url: `/system/tenant/crateTenantCode/${data.tenantCode}/${data.tenantExpirationDate}`,
+    method: 'get',
+  })
+}
+// 激活码激活租户
+export function activationOperation(data) {
+  return request({
+    url: `/system/tenant/activationOperation/${data.tenantId}/${data.tenantExpirationTime}`,
+    method: 'get',
+  })
+}

+ 274 - 0
ruoyi-ui/src/views/system/tenant/code/index.vue

@@ -0,0 +1,274 @@
+<template>
+  <div class="app-container">
+    <div>
+      <el-form ref="form" :model="activationCode" :rules="rulesActivationCode" label-width="80px">
+        <el-form-item label="租户信息" prop="tenantCode">
+          <el-select v-model="activationCode.tenantCode" placeholder="请选择">
+            <el-option
+              v-for="item in tenantList"
+              :key="item.tenantId"
+              :label="item.tenantName"
+              :value="item.tenantId">
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="充值时间" prop="tenantExpirationDate" style="width: 300px">
+          <el-input v-model="activationCode.tenantExpirationDate" placeholder="请输入充值天数"></el-input>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="crateCode">生成激活码</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+      <!-- 激活码生成成功-->
+      <el-dialog :title="title" :visible.sync="activationCodeForm" width="500px" append-to-body>
+        <span v-html="actCode"></span>
+        <div slot="footer" class="dialog-footer">
+          <el-button type="primary" @click="copyToClipboard">一键复制</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </el-dialog>
+    </div>
+    <div style="margin-top: 30px">
+
+      <!--搜索条件表单-->
+      <el-form
+        :model="queryParams"
+        ref="queryForm"
+        size="small"
+        :inline="true"
+        v-show="showSearch"
+        label-width="68px"
+      >
+        <el-form-item label="登录地址" prop="ipAddress">
+          <el-input
+            v-model="queryParams.ipAddress"
+            placeholder="请输入登录地址"
+            clearable
+            style="width: 240px;"
+            @keyup.enter.native="handleQuery"
+          />
+        </el-form-item>
+        <el-form-item label="操作人员" prop="operator">
+          <el-input
+            v-model="queryParams.operator"
+            placeholder="请输入操作人员"
+            clearable
+            style="width: 240px"
+            @keyup.enter.native="handleQuery"
+          />
+        </el-form-item>
+        <el-form-item label="操作时间">
+          <el-date-picker
+            v-model="dateRange"
+            style="width: 240px"
+            value-format="yyyy-MM-dd HH:mm:ss"
+            type="daterange"
+            range-separator="-"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            :default-time="['00:00:00', '23:59:59']"
+          ></el-date-picker>
+        </el-form-item>
+
+        <el-form-item>
+          <el-button
+            type="primary"
+            icon="el-icon-search"
+            size="mini"
+            @click="handleQuery"
+          >搜索</el-button
+          >
+          <el-button icon="el-icon-refresh" size="mini" @click="resetQuery"
+          >重置</el-button
+          >
+        </el-form-item>
+      </el-form>
+
+      <!--表格-->
+      <el-table
+        ref="tables"
+        v-loading="loading"
+        :data="list"
+        @selection-change="handleSelectionChange"
+        :default-sort="defaultSort"
+        @sort-change="handleSortChange"
+        style="margin-top: 20px"
+      >
+
+        <el-table-column type="selection" width="50" align="center" />
+        <el-table-column label="日志编号" align="center" prop="logId" />
+        <el-table-column label="操作人员" align="center" prop="operator" />
+        <el-table-column
+          label="操作ip地址"
+          align="center"
+          prop="ipAddress"
+          :show-overflow-tooltip="true"
+        />
+        <el-table-column
+          label="操作日期"
+          align="center"
+          prop="generationTime"
+          sortable="custom"
+          :sort-orders="['descending', 'ascending']"
+        >
+          <template slot-scope="scope">
+            <span>{{ parseTime(scope.row.generationTime) }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="备注" align="center" prop="note">
+        </el-table-column>
+      </el-table>
+      <!--分页器-->
+      <pagination
+        v-show="total > 0"
+        :total="total"
+        :page.sync="queryParams.pageNum"
+        :limit.sync="queryParams.pageSize"
+        @pagination="getCodeLogList"
+      />
+    </div>
+  </div>
+</template>
+
+<script>
+import {
+  listTenant,
+  createTenantCode
+} from "@/api/system/tenant";
+import {list, delActivationLog, cleanActivationLog} from "@/api/monitor/activationcode";
+
+import Clipboard from 'clipboard';
+
+export default {
+  name: "TenantCode",
+  data() {
+    return {
+        options: [],
+        //表单校验
+        rulesActivationCode: {
+          tenantCode: [
+            { required: true, message: "未选择租户信息", trigger: "blur" },
+          ],
+          tenantExpirationDate: [
+            { required: true, message: "租户到期时间为设置", trigger: "blur" },
+          ],
+        },
+        activationCode: {},
+        //租户信息表格数据
+        tenantList: [],
+        activationCodeForm: false,
+        actCode:"",
+        title:"激活码生成成功",
+        // 遮罩层
+        loading: true,
+        // 选中数组
+        ids: [],
+        // 非多个禁用
+        multiple: true,
+        // 显示搜索条件
+        showSearch: true,
+        // 总条数
+        total: 0,
+        // 表格数据
+        list: [],
+        // 是否显示弹出层
+        open: false,
+        // 日期范围
+        dateRange: [],
+        // 默认排序
+        defaultSort: { prop: "operTime", order: "descending" },
+        // 表单参数
+        form: {},
+        // 查询参数
+        queryParams: {
+          pageNum: 1,
+          pageSize: 10,
+          operator: undefined,
+          ipAddress: undefined
+        },
+    }
+  },
+  computed: {
+
+  },
+  created() {
+    this.getList();
+    this.getCodeLogList();
+  },
+  methods: {
+    //查询租户信息列表
+    getList() {
+      listTenant(this.queryParams).then((response) => {
+        this.tenantList = response.rows;
+      });
+    },
+    //创建激活码
+    crateCode(){
+      createTenantCode(this.activationCode).then((response) => {
+         this.activationCodeForm=true;
+         this.actCode=response.msg;
+      });
+    },
+    //激活码复制操作
+    copyToClipboard() {
+      //创建一个新 Clipboard 实例,将目标元素和复制成功时的回调传递给它
+      const clipboard = new Clipboard('button', {
+        text: () => this.actCode, //在这里替换为你要复制的文本
+      });
+      clipboard.on('success', () => {
+        this.activationCodeForm=false;
+        console.log('');
+        this.$message({showClose: true, message: '文本已成功复制到剪贴板', type: 'success'});
+        clipboard.destroy(); //清除 Clipboard 实例
+      });
+      clipboard.on('error', () => {
+        console.error('复制失败');
+        clipboard.destroy(); //清除 Clipboard 实例
+      });
+      //触发按钮点击事件,开始复制操作
+      clipboard.onClick({
+        action: 'copy',
+      });
+    },
+    //取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    //激活码日志信息
+    getCodeLogList() {
+      this.loading = true;
+      list(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
+          this.list = response.rows;
+          this.total = response.total;
+          this.loading = false;
+        }
+      );
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.dateRange = [];
+      this.resetForm("queryForm");
+      this.queryParams.pageNum = 1;
+      this.$refs.tables.sort(this.defaultSort.prop, this.defaultSort.order);
+    },
+    /** 多选框选中数据 */
+    handleSelectionChange(selection) {
+      this.ids = selection.map((item) => item.logId);
+      this.multiple = !selection.length;
+    },
+    /** 排序触发事件 */
+    handleSortChange(column, prop, order) {
+      this.queryParams.orderByColumn = column.prop;
+      this.queryParams.isAsc = column.order;
+      this.getList();
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getCodeLogList();
+    },
+  },
+};
+</script>

+ 69 - 5
ruoyi-ui/src/views/system/tenant/index.vue

@@ -177,6 +177,14 @@
               </el-dropdown-item>
             </el-dropdown-menu>
           </el-dropdown>
+          <el-button
+            size="small"
+            type="warning"
+            icon="el-icon-edit"
+            @click="openTenantExpirationTime(scope.row)"
+            v-hasPermi="['system:tenant:remove']"
+            style="margin-left:5px">激活租户
+          </el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -306,6 +314,19 @@
         <el-button @click="cancel1">取 消</el-button>
       </div>
     </el-dialog>
+
+    <!-- 激活租户弹出层-->
+    <el-dialog :title="title" :visible.sync="tenantExpirationTimeOpen" width="500px" append-to-body>
+      <el-form ref="form" :model="tenantExpirationTimeFrom" :rules="rulesTenantExpirationTime" label-width="80px">
+        <el-form-item label="激活租户" prop="tenantExpirationTime" label-width="110px">
+          <el-input v-model="tenantExpirationTimeFrom.tenantExpirationTime" placeholder="请输入激活码" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="activationOperationMethod">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
   </div>
 </template>
 
@@ -320,6 +341,7 @@ import {
   selectAllUser,
   createTenant,
   initTenantMenuData,
+  activationOperation
 } from "@/api/system/tenant";
 import { getDataSourceInfo, insertDataSource } from "@/api/system/data";
 import { servicesLoading } from "@/utils/ruoyi";
@@ -419,6 +441,16 @@ export default {
           { required: true, message: "端口号不能为空", trigger: "blur" },
         ],
       },
+      //租户激活码弹窗表单
+      tenantExpirationTimeFrom:{},
+      //租户激活码弹窗标题
+      tenantExpirationTimeOpen:false,
+      //租户激活码规则验证
+      rulesTenantExpirationTime: {
+        tenantExpirationTime: [
+          { required: true, message: "请输入租户激活码", trigger: "blur" },
+        ],
+      },
     };
   },
   computed: {
@@ -451,12 +483,12 @@ export default {
     },
     // 数据库名称校验规则
     databaseNameValidator(rule, value, callback){
-      let regex = /^[a-z][a-z0-9]*$/; 
-      if (regex.test(value)) {  
-        callback(); // 输入内容符合规则  
-      } else {  
+      let regex = /^[a-z][a-z0-9]*$/;
+      if (regex.test(value)) {
+        callback(); // 输入内容符合规则
+      } else {
         callback(new Error("只能包含小写字母和数字,且以小写字母开头"));
-      }  
+      }
     },
     // 数据源类型改变回调
     dataSourceTypeChange(type) {
@@ -702,6 +734,38 @@ export default {
         console.log(res);
       });
     },
+    //重置租户激活码表单
+    restTenantExpirationTime() {
+      this.tenantExpirationTimeFrom = {
+        tenantId: null,
+        tenantExpirationTime: null,
+      };
+      this.resetForm("tenantExpirationTimeFrom");
+    },
+    /**打开激活码弹窗*/
+    openTenantExpirationTime(row) {
+      this.reset();
+      console.log(this)
+      const tenantId = row.tenantId || this.ids;
+      console.log(tenantId)
+      this.tenantExpirationTimeFrom.tenantId= row.tenantId
+      this.tenantExpirationTimeOpen=true;
+      this.title="激活租户";
+    },
+    /**激活租户操作*/
+    activationOperationMethod(){
+      console.log("激活租户");
+      console.log(this.tenantExpirationTimeFrom.tenantExpirationTime);
+      this.$refs["form"].validate((valid) => {
+        console.log(valid)
+        if (valid) {
+          activationOperation(this.tenantExpirationTimeFrom).then((response) => {
+            this.$modal.msgSuccess("激活成功");
+            this.open = false;
+          });
+        }
+      });
+    }
   },
 };
 </script>