代码 - 账号密码加密登录(RSA加密传递AES密钥给用户,前端使用AES密钥进行加密内容给后台)

概述

思路

//思路
每个会话都有服务器送过来的RSA私钥,然后服务器在用会话的RSA公钥加密一把AES密钥给浏览器用户(会话),浏览器拿到加密的AES密钥,在用前面后台给的RSA私钥进行解密出AES密钥,登录时账号密码则用前面的AES密钥进行加密 即可 

工具包

AES前端加解密那个包官方没有提供直接浏览器可引入使用的js包,我自行打包的,大家可直接使用

链接: https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-https://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQhttps://pan.baidu.com/s/1SGygD0HYYMAdwl-aqEnFAQ?pwd=gnwq 提取码: gnwq 复制这段内容后打开百度网盘手机App,操作更方便哦 
--来自百度网盘超级会员v5的分享

工具包
后端:Hutool - https://www.hutool.cn/
前端
crypto-js(AES):https://github.com/brix/crypto-js
jsencrypt(RSA):https://github.com/travist/jsencrypt
Lodash(工具包):https://github.com/lodash/lodash

代码

后端

UserController.java

package work.linruchang.lrcutilsweb.controller;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.lang.func.LambdaUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import work.linruchang.lrcutilsweb.bean.User;
import work.linruchang.lrcutilsweb.config.handler.ResponseResult;
import work.linruchang.lrcutilsweb.config.handler.SystemBaseCustomException;
import work.linruchang.lrcutilsweb.consts.*;
import work.linruchang.lrcutilsweb.controller.base.BaseModelController;
import work.linruchang.lrcutilsweb.controller.vo.LoginQO;
import work.linruchang.lrcutilsweb.controller.vo.OauthCallback;
import work.linruchang.lrcutilsweb.controller.vo.OauthPlatformInfoVO;
import work.linruchang.lrcutilsweb.controller.vo.RegisterQO;
import work.linruchang.lrcutilsweb.service.CacheService;
import work.linruchang.lrcutilsweb.service.SysDictService;
import work.linruchang.lrcutilsweb.service.UserService;
import work.linruchang.lrcutilsweb.util.EnhanceJWTUtil;
import work.linruchang.lrcutilsweb.util.EnhanceSecureUtil;
import work.linruchang.lrcutilsweb.util.EnhanceSpringUtil;
import work.linruchang.lrcutilsweb.util.oauth.OauthPlatform;
import work.linruchang.lrcutilsweb.util.oauth.OauthPlatformFactory;
import work.linruchang.lrcutilsweb.util.oauth.OauthUser;

import javax.crypto.SecretKey;
import java.io.Serializable;
import java.security.KeyPair;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;


/**
 * 用户接口层
 *
 * @author lrc
 * @version 1.0
 * @date 2022-10-28
 * @since 1.8
 **/
@Api(tags = "用户模块")
@Controller
@RequestMapping("user")
@Slf4j
public class UserController implements BaseModelController<User> {

    @Autowired
    UserService userService;

    @Autowired
    SysDictService sysDictService;

    @Autowired
    CacheService cacheService;



    @ApiOperation(value = "生成获取会话用户的RSA私钥")
    @GetMapping("rsa-private-key")
    @ResponseBody
    public ResponseResult<String> getRSAPrivateKey() {
        String sessionId = EnhanceSpringUtil.getCurrrentRequest().getSession().getId();

        KeyPair rsa = EnhanceSecureUtil.generateKeyPair("RSA");
        String publicKey = Base64.encode(rsa.getPublic().getEncoded());
        String privateKey = Base64.encode(rsa.getPrivate().getEncoded());
        cacheService.set(CacheKeyConstant.SESSION_RSA_PRIVATE_KEY + sessionId, privateKey);
        cacheService.set(CacheKeyConstant.SESSION_RSA_PUBLIC_KEY + sessionId, publicKey);
        log.info("会话【{}】RSA密钥对 \n公钥:\n{}\n私钥:\n{}", sessionId, publicKey, privateKey);
        return ResponseResult.success(privateKey);
    }

    @ApiOperation(value = "生成获取会话用户的AES密钥(经过会话用户的RSA私钥进行加密)")
    @GetMapping("aes-key")
    @ResponseBody
    public ResponseResult<String> getAESKey() {
        String sessionId = EnhanceSpringUtil.getCurrrentRequest().getSession().getId();

        String sessionRsaPrivateKey = cacheService.get(CacheKeyConstant.SESSION_RSA_PRIVATE_KEY + sessionId);
        String sessionRsaPublicKey = cacheService.get(CacheKeyConstant.SESSION_RSA_PUBLIC_KEY + sessionId);
        Assert.notBlank(sessionRsaPrivateKey, () -> new SystemBaseCustomException(RequestEnum.ResponseCodeEnum.TOKEN_SESSION_RSA_PRIVATE_KEY_ERROR, sessionId));
        RSA rsa = new RSA(sessionRsaPrivateKey, sessionRsaPublicKey);


        SecretKey aesSecretKey = EnhanceSecureUtil.generateAESKey();
        // String aesKey = Base64.encode(aesSecretKey.getEncoded());
        String aesKey = new String(aesSecretKey.getEncoded());
        cacheService.set(CacheKeyConstant.SESSION_AES_KEY + sessionId, aesKey);

        String encryAesKey = rsa.encryptBase64(aesKey, KeyType.PublicKey);
        log.info("会话【{}】AES密钥 \n未加密\n{}\n加密(RSA公钥)\n{}", sessionId, aesKey, encryAesKey);
        return ResponseResult.success(encryAesKey);
    }


    @ApiOperation(value = "登录")
    @PostMapping("login")
    @ResponseBody
    public ResponseResult<User> login(@RequestBody LoginQO loginQO) {
        String sessionId = EnhanceSpringUtil.getCurrrentRequest().getSession().getId();

        String aesKey = cacheService.get(CacheKeyConstant.SESSION_AES_KEY + sessionId);
        Assert.notBlank(aesKey, () -> new SystemBaseCustomException(RequestEnum.ResponseCodeEnum.TOKEN_SESSION_AES_KEY_ERROR, sessionId));

        // 解密密码
        String originalPassword = EnhanceSecureUtil.aesDecryptStr(aesKey, loginQO.getPassword());
        loginQO.setPassword(originalPassword);

        User existUser = userService.getOne(Wrappers.<User>lambdaQuery()
                .eq(User::getLoginName, loginQO.getLoginName())
                .eq(User::getPassword, loginQO.getPassword()));

        if (existUser != null) {
            String token = userService.createToken(existUser);
            existUser.setToken(token);
            existUser.setPassword(null);
            return ResponseResult.success(existUser);
        } else {
            return ResponseResult.fail(null, "登录失败:账号或密码错误,请检查");
        }
    }


    @ApiOperation(value = "用户注册")
    @PostMapping("register")
    @ResponseBody
    public ResponseResult<Boolean> register(@RequestBody RegisterQO registerQO) {
        userService.registerVerify(registerQO);
        return ResponseResult.success(true);
    }

}


EnhanceSecureUtil.java

package work.linruchang.lrcutilsweb.util;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.KeyUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;

import javax.crypto.SecretKey;
import java.util.stream.IntStream;

/**
 * 增强的安全相关工具类
 * @author LinRuChang
 * @version 1.0
 * @date 2022/11/13
 * @since 1.8
 **/
public class EnhanceSecureUtil extends SecureUtil {

    public static byte[] recognizableByteArray = new String("qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM123456789[]{};':,./<>?!@#$%^&*()_+").getBytes();

    public static Integer AES_BYTE_SIZE_16 = 16;
    public static Integer AES_BYTE_SIZE_24 = 24;
    public static Integer AES_BYTE_SIZE_32 = 32;

    /**
     * 生成AES密钥
     */
    public static String generateAESKeyStr() {
        return new String(generateAESKey(AES_BYTE_SIZE_32).getEncoded());
    }

    /**
     * 生成AES密钥
     * @return  获取密钥 {@link java.security.Key#getEncoded()}
     */
    public static SecretKey generateAESKey() {
        return generateAESKey(AES_BYTE_SIZE_32);
    }

    /**
     * 生成AES密钥
     * @param byteSize 密钥字节长度{@link EnhanceSecureUtil#AES_BYTE_SIZE_16}
     * @return 获取密钥 {@link java.security.Key#getEncoded()}
     */
    public static SecretKey generateAESKey(int byteSize) {
        Assert.isTrue(ArrayUtil.contains(new Integer[]{16,24,32},byteSize), "密钥字节数组的长度必须是16、24或32字节长度, 请检查");
        byte[] key = new byte[byteSize];
        IntStream.range(0,byteSize)
                .forEach(keyIndex -> {
                    int recognizableByteArrayIndex = RandomUtil.randomInt(0, recognizableByteArray.length);
                    key[keyIndex] = recognizableByteArray[recognizableByteArrayIndex];
                });
        return generateAESKey(key);
    }

    /**
     *
     * AES密钥长度必须是:16、24、32字节数组
     * @param key 密钥
     * @return 获取密钥 {@link java.security.Key#getEncoded()}
     */
    public static SecretKey generateAESKey(byte[] key) {
        Assert.isTrue(ArrayUtil.contains(new Integer[]{16,24,32},ArrayUtil.length(key)), "密钥字节数组的长度必须是16、24或32字节长度, 请检查");
        return KeyUtil.generateKey(SymmetricAlgorithm.AES.getValue(), key);
    }

    /**
     * 构造,使用默认的AES/ECB/PKCS5Padding
     * @param key 密钥
     * @return
     */
    public static AES aes(String key) {
        byte[] keyBytes = key.getBytes();
        Assert.isTrue(ArrayUtil.contains(new Integer[]{16,24,32},ArrayUtil.length(keyBytes)), "密钥字节数组的长度必须是16、24或32字节长度, 请检查");
        return new AES(keyBytes);
    }


    /**
     * AES解密
     * @param key AES密钥
     * @param rowContent 密文
     * @return
     */
    public static String aesDecryptStr(String key, String rowContent) {
        if(StrUtil.isAllNotBlank(key,rowContent)) {
            return EnhanceSecureUtil.aes(key).decryptStr(rowContent);
        }
        return rowContent;
    }

}

前端

smallUtil.js

/**
 * 字符串是否不为空
 * @param content 字符串内容
 */
function strIsNotEmpty(content) {
    let result = content && content.length > 0;
    if( (typeof result) === 'string' ) {
        return result.length > 0;
    }else {
        return result ? true : false ;
    }
}

/**
 * 字符串是否为空
 * @param content 字符串内容
 */
function strIsEmpty(content) {
    return !strIsNotEmpty(content);
}

/**
 * 字符串是空则返回默认字符串内容
 * @param content 字符串内容
 * @param defaultContent 默认字符串内容
 */
function strEmptyToDefault(content, defaultContent) {
    return strIsNotEmpty(content) ? content : defaultContent;
}








/**
 * AES加密
 * <a href="https://www.npmjs.com/package/cryptojs" />
 * @param plaintext 明文
 * @param aesKey 密钥
 * @returns 经过Base64编码的密文
 */
function aesEncrypt(plaintext, aesKey) {
    if(strIsNotEmpty(plaintext) && strIsNotEmpty(aesKey) ) {
        const __key = CryptoJS.enc.Utf8.parse(aesKey) //将秘钥转换成Utf8字节数组
        const encrypt = CryptoJS.AES.encrypt(String(plaintext), __key, {
            // iv: iv,
            mode: CryptoJS.mode.ECB,
            padding: CryptoJS.pad.Pkcs7
        })
        return encrypt.toString()
    }
    log.warn("AES加密:明文以及密钥不可为空,请检查");
    return null;
}

/**
 * AES加密(密钥取自本地LocalStorage)
 * <a href="https://www.npmjs.com/package/cryptojs" />
 * @param plaintext 明文
 * @param aesKey 密钥
 * @returns 经过Base64编码的密文
 */
function aesEncryptBySessionKey(plaintext) {
    return aesEncrypt(plaintext, localStorage.getItem(systemConst.session.AESKey))
}

/**
 * AES加密(密钥取自本地LocalStorage)
 * <a href="https://www.npmjs.com/package/cryptojs" />
 * @param plaintext 明文
 * @param aesKey 密钥
 * @returns 经过Base64编码的密文
 */
function aesEncryptJsonBySessionKey(jsonObj) {
    let jsonObjCopy = _.cloneDeep(jsonObj)
    if(jsonObjCopy) {
        let keys = _.keys(jsonObjCopy);
        if(keys && keys.length > 0) {
            keys.forEach((keyName,keyIndex) => {
                if(jsonObjCopy[keyName] &&  (typeof jsonObjCopy[keyName]) === 'string'  && strIsNotEmpty(jsonObjCopy[keyName])) {
                    jsonObjCopy[keyName] = aesEncryptBySessionKey(jsonObjCopy[keyName])
                }
            })
        }
    }
    return jsonObjCopy;
}



/**
 * AES解密
 * <a href="https://www.npmjs.com/package/cryptojs" />
 * @param plaintext 经过Base64编码的密文
 * @param aesKey 密钥
 * @returns 明文
 */
function aesDecrypt(ciphertext, aesKey) {
    if(strIsNotEmpty(ciphertext) && strIsNotEmpty(aesKey) ) {
        const __key = CryptoJS.enc.Utf8.parse(aesKey) //将秘钥转换成Utf8字节数组
        const decrypt = CryptoJS.AES.decrypt(ciphertext, __key, {
            mode: CryptoJS.mode.ECB,
            padding: CryptoJS.pad.Pkcs7
        })
        return decrypt.toString(CryptoJS.enc.Utf8) //解密后的数据
    }
    log.warn("AES解密:密文以及密钥不可为空,请检查");
    return null;
}

/**
 * RSA解密
 * (jsencrypt工具仅支持公钥加密、私钥解密,私钥加密、但不支持公钥解密(反正就是很迷惑))
 * <a href="https://www.npmjs.com/package/jsencrypt" />
 * @param plaintext 密文
 * @param aesKey RSA公钥(Base64编码)
 * @returns 明文
 */
function RSADecryptByPrivateKey(ciphertext, privateKey) {
    if(strIsNotEmpty(ciphertext) && strIsNotEmpty(privateKey)) {
        const encryptor = new JSEncrypt()
        encryptor.setPrivateKey(privateKey) // 设置公钥
        return encryptor.decrypt(ciphertext);
    }
    log.warn("RSA解密:密文以及密钥不可为空,请检查");
    return null;
}



index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>个人在线工具</title>
    <link rel="stylesheet" href="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/css/index.css"/>
    <link rel="stylesheet" href="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/css/element-plus.css"/>
    <link rel="stylesheet"
          href="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/css/generateShortLinkPage.css"/>
    <link rel="stylesheet" href="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/css/figureBedPage.css"/>
    <link rel="stylesheet" href="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/css/generateCodePage.css"/>
    <link rel="stylesheet"
          href="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/css/systemSettingPage.css"/>
    <link rel="stylesheet"
          href="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/css/updateTimelinePage.css"/>
    <link rel="stylesheet"
          href="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/css/articleDetailPage.css"/>
    <link rel="stylesheet" href="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/css/articleListPage.css"/>

    <!-- remarkable -->
    <!-- <link rel="stylesheet" href="/js/highlight-11.6.0_all-language/styles/vs.min.css" /> -->
    <!-- markdown-it -->
    <link rel="stylesheet" href="/js/highlight-11.6.0_all-language/styles/atom-one-dark.min.css"/>


</head>
<body>
<div id="vueApp">


    <el-container>
        <el-aside width="13rem">

            <el-menu
                    default-active="2"
                    unique-opened
                    router
                    class="leftElMenu el-menu-vertical-demo"
                    :collapse="isCollapse"
                    @click.self="switchCollapse"
            >

                <template v-for="menuData in menuDatas">

                    <!-- 有二级菜单 -->
                    <el-sub-menu v-if="menuData.childrens && menuData.childrens.length > 0"
                                 :index="getMenuIndex(menuData)">
                        <template #title>
                            <el-icon v-if="menuData.menuIcon == 'icon-menu'">
                                <icon-menu/>
                            </el-icon>
                            <el-icon v-if="menuData.menuIcon == 'location'">
                                <location/>
                            </el-icon>
                            <el-icon v-if="menuData.menuIcon == 'setting'">
                                <setting/>
                            </el-icon>
                            <el-icon v-if="menuData.menuIcon == 'notebook'">
                                <notebook/>
                            </el-icon>
                            <span>{{menuData.name}}</span>
                        </template>


                        <template v-for="childrenMenuData in menuData.childrens">
                            <el-menu-item-group v-if="childrenMenuData.groupTitle">
                                <template #title><span>{{childrenMenuData.groupTitle}}</span></template>
                                <el-menu-item :index="getMenuIndex(childrenMenuData)">{{childrenMenuData.name}}
                                </el-menu-item>
                            </el-menu-item-group>

                            <el-menu-item
                                    v-else-if="!childrenMenuData.childrens || childrenMenuData.childrens.length == 0"
                                    :index="getMenuIndex(childrenMenuData)">{{childrenMenuData.name}}
                            </el-menu-item>

                            <el-sub-menu v-else :index="childrenMenuData.routerPath || childrenMenuData.id">
                                <template #title><span>{{childrenMenuData.name}}</span></template>
                                <el-menu-item v-for="childrenMenuDataChildren in  childrenMenuData.childrens"
                                              :index="getMenuIndex(childrenMenuDataChildren)">
                                    {{childrenMenuDataChildren.name}}
                                </el-menu-item>
                            </el-sub-menu>
                        </template>

                    </el-sub-menu>

                    <!-- 一级菜单 -->
                    <el-menu-item v-else :index="getMenuIndex(menuData)">
                        <el-icon v-if="menuData.menuIcon == 'icon-menu'">
                            <icon-menu/>
                        </el-icon>
                        <el-icon v-if="menuData.menuIcon == 'location'">
                            <location/>
                        </el-icon>
                        <el-icon v-if="menuData.menuIcon == 'setting'">
                            <setting/>
                        </el-icon>
                        <el-icon v-if="menuData.menuIcon == 'notebook'">
                            <notebook/>
                        </el-icon>
                        <template #title>{{menuData.name}}</template>
                    </el-menu-item>
                </template>
            </el-menu>

        </el-aside>
        <el-container>

            <el-header class="elHeader">

                <div class="web-logo-box">
                    <el-avatar style="vertical-align: middle" shape="square" size="default" fit="fit"
                               src="/img/webLogo.jpg"></el-avatar>
                    <span class="web-name">个人在线工具</span>
                </div>

                <div style="margin-right: 13rem">
                    <div class="loginUserInfoBox" v-if="loginUserInfo">

                        <el-popover placement="bottom-end" :width="300" trigger="click">
                            <template #reference>
                                <el-avatar shape="circle" size="default" fit="fit" :src="loginUserInfo.headPortrait?loginUserInfo.headPortrait:'/img/defaultHeadPortrait.webp'"/>
                            </template>
                            <div class="el-popover-loginUserInfoBox-content">
                                <div class="el-popover-loginUserInfoBox-content-row userInfo">
                                    <el-avatar shape="circle" size="default" fit="fit"
                                               :src="loginUserInfo.headPortrait?loginUserInfo.headPortrait:'/img/defaultHeadPortrait.webp'"></el-avatar>
                                    <div class="detailInfo">
                                        <span style="font-size: 1.1rem;font-weight: bolder">{{loginUserInfo.nickname}}</span>
                                        <span>加入时间:{{loginUserInfo.createTime}}</span>
                                    </div>
                                </div>
                                <div class="el-popover-loginUserInfoBox-content-row btnBox">
                                    <el-button type="danger" @click="logout">退出登录</el-button>
                                </div>
                            </div>
                        </el-popover>


                    </div>
                    <el-button-group v-else class="ml-4" class="headerButtonGroup">
                        <el-button type="primary" @click="openLoginDialog">登录</el-button>
                        <el-button type="primary" @click="openRegisterDialog">注册</el-button>
                    </el-button-group>
                </div>


                <!-- 模仿掘金的登录样式 -->
                <el-dialog align-center class="loginRegisterDialog" v-model="loginRegisterDialog"
                           :title="loginFlag ? '登录' : '注册'" width="18%" destroy-on-close>

                    <div class="loginRegisterIconBox">
                        <img data-v-7b44b39a="" src="/img/loginIcon.svg" class="normal" style="">
                    </div>

                    <el-row>
                        <el-form class="loginRegisterForm" ref="loginRegisterFormRef" :model="loginInfo"
                                 :rules="loginInfoRules">

                            <el-form-item prop="loginName">
                                <el-input v-model="loginInfo.loginName" placeholder="账号"></el-input>
                            </el-form-item>
                            <el-form-item prop="password">
                                <el-input v-model="loginInfo.password" placeholder="密码"></el-input>
                            </el-form-item>

                            <template v-if="!loginFlag">
                                <el-form-item prop="email" class="emailFormItem">
                                    <el-input v-model="loginInfo.email" placeholder="邮箱"></el-input>
                                    <el-button @click="getRegisterEmailVerifyCode" :disabled="registerEmailSended">{{registerEmailSended ? reSendRegisterEmailSecond + '秒后重新发送' : '获取验证码'}}</el-button>
                                </el-form-item>
                                <el-form-item prop="emailVerificationCode" >
                                    <el-input v-model="loginInfo.emailVerificationCode"
                                              placeholder="邮箱验证码"></el-input>
                                </el-form-item>
                            </template>

                            <el-button type="primary" class="loginRegisterBtn  loginRegisterDialogRow"
                                       @click="submitLoginRegister" v-text="loginFlag?'登录':'注册'">
                                       <!-- @click="submitLoginRegister">{{loginFlag?'登录':'注册'}} -->
                            </el-button>


                            <el-row class="otherLoginWayBtn loginRegisterDialogRow " v-if="loginFlag">其他登录方式
                            </el-row>
                            <el-row class="avatar-boxs-row" v-if="loginFlag">
                                <div class="avatar-box"
                                     v-for="(oauthPlatformInfoVO,oauthPlatformInfoVOIndex) in oauthPlatformInfoVOs"
                                     :key="oauthPlatformInfoVOIndex"
                                     @click="jumpOauthPlatformAuthUrl(oauthPlatformInfoVO)">
                                    <div v-if="!oauthPlatformInfoVO.supportFlag" class="noSupportMask"></div>
                                    <el-avatar shape="circle" size="small" fit="fit" :name="oauthPlatformInfoVO.name"
                                               :code="oauthPlatformInfoVO.code" :src="oauthPlatformInfoVO.icon"/>
                                </div>
                            </el-row>
                        </el-form>
                    </el-row>
                </el-dialog>

            </el-header>

            <el-main>
                <router-view></router-view>
            </el-main>

            <el-footer class="footerBox" v-if="webSiteFooterInfoVO.recordNumber">
                <div clas="websiteRecordsBox">
                    <el-link :href="webSiteFooterInfoVO.recordLink" target="_blank">
                        {{webSiteFooterInfoVO.recordNumber}}
                    </el-link>
                </div>
                <div class="youPaiYunBox"
                     v-if="webSiteFooterInfoVO.youPaiYunLinkFlag && webSiteFooterInfoVO.youPaiYunLinkFlag == '1'">
                    <el-link :href="webSiteFooterInfoVO.youPaiYunLink" target="_blank">
                        本网站由 <img class="youPaiYunLogo" src="/img/又拍云_logo2.png"/> 提供CDN加速/云存储服务
                    </el-link>
                </div>
            </el-footer>
        </el-container>
    </el-container>


</div>
</body>


<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/vue3.2.37.global.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/vue-router.4.1.5.global.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/axios.min.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/qs.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/element-plus.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/elementui-plus-icons-vue.min.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/element-plus-zh-cn.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/jquery.min.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/layer.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/copy-to-clipboardPublic.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/dayjs.min.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/lodash.js"></script>

<!-- md转html、以及代码高亮 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/remarkable.min.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/highlight-11.6.0_all-language/highlight.min.js"></script>


<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/markdown-it/markdown-it.13.0.1.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/markdown-it/markdownItAnchor.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/markdown-it/markdownItTocDoneRight.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/markdown-it/markdown-it-textual-uml.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/markdown-it/markdown-it-code-copy.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/markdown-it/markdown-it-emoji.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/markdown-it/mermaid.js"></script>

<!-- json转yml -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/json2yaml.js"></script>
<!-- html转markdown -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/turndown.js"></script>
<!-- 仅支持xml美化==可直接抛弃使用==smallUtil.xmlFormat -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/xml-formatter.js"></script>
<!-- 仅支持json美化==可直接抛弃使用==smallUtil.jsonFormat -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/json-beautify.js"></script>
<!-- CSS/JS/JSON/HTML/XML的美化==smallUtil.formatAny -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/beautify.js"></script>
<!-- sql美化 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/sqlformatter.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/md5.min.js"></script>


<!-- cookie操作 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/js.cookie.min.js"></script>

<!-- AES加解密 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/crypto-js.js"></script>
<!-- RSA加解密 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/js/jsencrypt.min.js"></script>

<!-- 假数据 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/mockData/mockData.js"></script>

<!-- 工具类部分 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/utils/systemConst.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/utils/smallUtil.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/utils/simpleAxios.js"></script>

<!-- 自定义组件部分 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/component/myTimeline/myTimeline.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/component/myTimeline/myTimelineItem.js"></script>

<!-- 页面部分 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/view/errorPage.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/view/resourceNotExistPage.js"></script>
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/view/notPermissionsPage.js"></script>

<!-- 短链生成器 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/view/generateShortLinkPage.js"></script>
<!-- 图床 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/view/figureBedPage.js"></script>
<!-- 代码生成器 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/view/generateCodePage.js"></script>
<!-- 系统更新时间线 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/view/updateTimelinePage.js"></script>
<!-- 系统设置 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/view/systemSettingPage.js"></script>
<!-- 文章列表页面 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/view/articleListPage.js"></script>
<!-- 文章详情页面 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/view/articleDetailPage.js"></script>


<!-- 系统接口 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/utils/systemApi.js"></script>
<!-- 页面路由器 -->
<script src="${webPageConfig.cdnSwitchFlag?then(webPageConfig.cdn!, '')}/router/my-router.js"></script>

<script>

    window.onload = async function () {

        //用户菜单 == 当前仅支持渲染二级,后台已经支持渲染多级的数据
        let {data} = await getUserMenu()
        let menuDatas = data.result;
        log('用户菜单:', menuDatas)

        const {createApp} = Vue
        const app = createApp({
            data() {
                return {
                    //侧边栏是否不展开
                    isCollapse: true,
                    menuDatas: menuDatas,
                    webSiteFooterInfoVO: {},

                    recordOldValue: {},
                    observer: null,

                    //登录注册对话框
                    loginRegisterDialog: false,
                    //是否登录状态
                    loginFlag: true,

                    loginInfo: {
                        loginName: null,
                        password: null,
                        email: null,
                        emailVerificationCode: null,
                    },

                    loginInfoRules: {
                        loginName: [{
                            required: true,
                            message: '请输入账号',
                            trigger: 'change',
                        }],
                        password: [{
                            required: true,
                            message: '请输入密码',
                            trigger: 'change',
                        }],
                        email: [{
                            required: true,
                            message: '请输入邮箱',
                            trigger: 'change',
                        }],
                        emailVerificationCode: [{
                            required: true,
                            message: '请输入邮箱验证码',
                            trigger: 'change',
                        }]
                    },

                    oauthPlatformInfoVOs: [],

                    loginUserInfo: null,

                    registerEmailSended: false,
                    reSendRegisterEmailSecond: null,
                }
            },
            methods: {

                /**
                 * 获取注册验证码
                 */
                getRegisterEmailVerifyCode: async function () {
                    if(strIsEmpty(this.loginInfo.email)) {
                        warningMessage("请先输入您的邮箱")
                        return;
                    }

                    let response = await sendEmail({
                        emailType: systemConst.emailType.register,
                        targetEmail: this.loginInfo.email
                    })

                    if (axiosResponseSuccess(response, "邮件发送成功", "邮件发送失败")) {
                        log("注册邮件发送:", response)
                        this.reSendRegisterEmailSecond  = response.data.result ?  (response.data.result)/1000  : 60;
                        this.registerEmailSended = true;
                        let reSendRegisterEmailSecondInterval = setInterval(() => {
                            this.reSendRegisterEmailSecond = this.reSendRegisterEmailSecond - 1;
                            if(this.reSendRegisterEmailSecond<=0) {
                                log("可重新发送邮件状态")
                                clearInterval(reSendRegisterEmailSecondInterval);
                                this.registerEmailSended = false;
                                this.reSendRegisterEmailSecond = null;
                            }
                        },1000)
                    }

                },

                logout: function () {
                    messageConfirm("确定退出登录?", () => {
                        //删除本地以及系统中的Token
                        logout();
                        this.loginUserInfo = null;
                        localStorage.removeItem("token");

                        //重新加载页面
                        window.location = '/'
                    })
                },

                submitLoginRegister: async function () {
                    // warningMessage("抱歉,当前系统暂不支持【账号密码】方式进行登录,敬请期待后续支持")
                    // return;

                    //登录

                    await this.$refs.loginRegisterFormRef.validate(async (valid, fields) => {
                        log("登录表单校验状态:", valid, fields)
                        if (valid) {
                            let loginInfoCopy = _.cloneDeep(this.loginInfo);
                            if (this.loginFlag) { //登录
                                loginInfoCopy.password = aesEncryptBySessionKey(loginInfoCopy.password)
                                let loginResponse = await login(loginInfoCopy)
                                if (axiosResponseSuccess(loginResponse, "登录成功", "登录失败")) {
                                    let loginUserInfo = loginResponse.data.result;
                                    log("登录成功信息:", loginUserInfo);
                                    localStorage.setItem(systemConst.cookie.token, loginUserInfo.token);
                                    //基于Token获取用户信息
                                    this.getUserInfo()

                                    //关闭登录弹框
                                    this.loginRegisterDialog = false;
                                }
                            } else { //注册
                                let registerResponse = await register(loginInfoCopy)
                                if (axiosResponseSuccess(registerResponse, "注册成功", "注册失败")) {
                                    this.loginInfo = {
                                        loginName: null,
                                        password: null,
                                        email: null,
                                        emailVerificationCode: null,
                                    }
                                    this.loginFlag = true

                                    this.$refs.loginRegisterFormRef.resetFields();
                                }
                            }
                        } else {
                            warningMessage("请根据提示填入对应的登录信息")
                        }
                    })
                },

                /**
                 * 获取并跳转Oauth平台的授权链接
                 * @param oauthPlatformInfoVO 平台类型信息
                 * @returns {Promise<void>}
                 */
                jumpOauthPlatformAuthUrl: async function (oauthPlatformInfoVO) {
                    if (!oauthPlatformInfoVO.supportFlag) {
                        warningMessage("抱歉,当前系统暂不支持【Oauth-" + oauthPlatformInfoVO.name + "】方式进行登录,敬请期待后续支持")
                        return;
                    }

                    let response = await getOauthPlatformAuthUrl(oauthPlatformInfoVO.code)
                    log('获取Oauth平台的授权链接', oauthPlatformInfoVO, response)
                    if (axiosResponseSuccess(response, null, "获取Oauth平台授权链接失败")) {
                        window.location = response.data.result;
                    }
                },


                /**
                 * 打开登录弹框
                 */
                openLoginDialog: function () {
                    this.loginRegisterDialog = true;
                    this.loginFlag = true;
                },

                /**
                 * 打开注册弹框
                 */
                openRegisterDialog: function () {
                    this.loginRegisterDialog = true;
                    this.loginFlag = false;
                },

                switchCollapse: function () {
                    this.isCollapse = !this.isCollapse;
                },

                getIcon(iconName) {
                    return '<' + iconName + '/>'
                },

                getMenuIndex(menuData) {
                    // return menuData.routerPath || menuData.id;
                    return _.toString(menuData.routerPath || menuData.id);
                },

                changeStyle: function () {
                    listeningElemStyleChange("html", (element) => {
                        let width = str2Num(getComputedStyle(element).getPropertyValue('width'));
                        let height = str2Num(getComputedStyle(element).getPropertyValue('height'));
                        if (width === this.recordOldValue.width && height === this.recordOldValue.height) {
                            return
                        }
                        log("html元素尺寸发生变化:", this.recordOldValue, {width, height})
                        this.recordOldValue = {
                            width,
                            height
                        }
                        this.$nextTick(() => {
                            if (window.innerHeight > $("html").height()) {
                                $(".footerBox").addClass("outerWindowHeight");
                            } else {
                                $(".footerBox").removeClass("outerWindowHeight");
                            }
                        })
                    })
                },

                /**
                 * 获取Oauth授权平台信息
                 * @returns {Promise<void>}
                 */
                getOauthPlatformInfos: async function () {
                    let response = await getOauthPlatformInfos()
                    log("获取Oauth平台信息", response)
                    if (axiosResponseSuccess(response, null, "获取Oauth平台信息失败")) {
                        this.oauthPlatformInfoVOs = response.data.result;
                    }
                },

                /**
                 * 获取用户信息
                 * @returns {Promise<void>}
                 */
                getUserInfo: async function () {
                    let token = getUrlParam(systemConst.cookie.token)

                    let firstLoginFlag = false;
                    if (strIsNotEmpty(token)) {
                        localStorage.setItem(systemConst.cookie.token, token)
                        firstLoginFlag = true;
                    }

                    token = token || localStorage.getItem(systemConst.cookie.token)

                    //如果从请求以及本地都无发现token则直接退出即可
                    if (strIsEmpty(token)) {
                        return;
                    }


                    //校验用户的有效性
                    let response = await tokenVerification()
                    log("校验Token有效性", token, response)
                    if (axiosResponseSuccess(response)) {
                        //校验通过获取用户信息
                        if (firstLoginFlag) {
                            window.location = '/'
                        } else {
                            response = await getUserInfo()
                            log("获取当前请求的用户信息", token, response)
                            if (axiosResponseSuccess(response), "登录成功") {
                                this.loginUserInfo = response.data.result;
                            }
                        }
                    } else {
                        localStorage.removeItem("token");
                    }

                },

                /**
                 * 获取会话用户的密钥(RSA公钥以及AES密钥)
                 * @returns {Promise<void>}
                 */
                getSessionKey: async function () {
                    let getRSAPrivateKeyResponse = await getRSAPrivateKey()
                    if (axiosResponseSuccess(getRSAPrivateKeyResponse), null, "当前会话的RSA公钥获取失败,请检查") {
                        let RSAPrivateKey = getRSAPrivateKeyResponse.data.result;
                        localStorage.setItem(systemConst.session.RSAPrivateKey, RSAPrivateKey)
                        log("会话RSA私钥:", RSAPrivateKey)
                        let getAESKeyResponse = await getAESKey()
                        if (axiosResponseSuccess(getAESKeyResponse), null, "当前会话的AES密钥获取失败,请检查") {
                            let encryAESKey = getAESKeyResponse.data.result;
                            log("会话AES密钥(加密):", encryAESKey)
                            let AESKey = RSADecryptByPrivateKey(encryAESKey, RSAPrivateKey)
                            log("会话AES密钥(解密):", AESKey)
                            localStorage.setItem(systemConst.session.AESKey, AESKey)
                        }
                    }
                }

            },

            mounted: async function () {

                //路由注册 == 不要放在这里否则使用链接进行访问页面时访问不了
                // registerRoute(menuDatas, myRouter)

                //网站页脚信息
                let response = await getWebSiteFooterInfoVO()
                if (axiosResponseSuccess(response)) {
                    this.webSiteFooterInfoVO = response.data.result;
                    this.changeStyle()
                }

                //获取用户信息
                this.getUserInfo();

                //Oauth平台信息
                this.getOauthPlatformInfos();


                //获取会话用户的密钥
                this.getSessionKey();

            }

        });

        // 是否屏蔽警告日志
        if (!getGlobalLogSwitch()) {
            app.config.warnHandler = (msg, instance, trace) => {
            }
        }

        //将全部图标设置为全局组件
        Object.keys(ElementPlusIconsVue).forEach((componentName) => {
            app.component(componentName, ElementPlusIconsVue[componentName]);
        })


        //路由注册
        // myRouter.beforeEach((to, from) => {
        //     log('当前打算去的路由:', to)
        //     if(!existRoutePath(to.fullPath, myRouter.getRoutes())) {
        //         registerRoute(menuDatas, myRouter)
        //         log("所有路由信息:", myRouter.getRoutes())
        //         //前面注册完,再次检测是否存有,没有,说明没权限访问的路由页面
        //         if(existRoutePath(to.fullPath,myRouter.getRoutes())) {
        //             return to.fullPath
        //         }else {
        //             return 'not-permissions-page'
        //         }
        //     }
        // })
        myRouter.beforeEach((to, from) => {
            log('当前打算去的路由:', to)
            if (!existRoutePath2(myRouter, to)) {
                registerRoute(menuDatas, myRouter)
                log("所有路由信息:", myRouter.getRoutes())
                //前面注册完,再次检测是否存有,没有,说明没权限访问的路由页面
                if (existRoutePath2(myRouter, to)) {
                    return to.fullPath
                } else {
                    return 'not-permissions-page'
                }
            }
        })


        app.use(ElementPlus, {locale: ElementPlusLocaleZhCn})
            .use(myRouter)
            .component("IconMenu", ElementPlusIconsVue.Menu)
            .component("MyTimeline", MyTimeline)
            .component("MyTimelineItem", MyTimelineItem)
            .mixin({  //图标变量全局混入注册
                data() {

                    let result = {
                        IconMenu: Vue.shallowRef(ElementPlusIconsVue.Menu)
                    }

                    _.keys(ElementPlusIconsVue).forEach(propertyName => {
                        result[propertyName] = Vue.shallowRef(ElementPlusIconsVue[propertyName])
                    })


                    return result;
                    // return {
                    //     ...ElementPlusIconsVue,
                    //     IconMenu: ElementPlusIconsVue.Menu
                    // }
                },
            })
            .mount('#vueApp');


    };


</script>

</html>

版权声明:本文为weixin_39651356原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。