SpringBoot + Vue +Element UI 实现注册登录的功能

兔子系统1.0

        这里是接着上次 【SpringBoot+Vue 项目创建详细步骤】 继续写的注册登录的功能,在写的过程中,对数据库的字段进行过更改,第一次改库的时候把上次发的文章里面的内容也改过,后来调整次数过多,怕改炸了,导致【SpringBoot+Vue 项目创建详细步骤】里面的代码不能正常运行了,所以干脆不改了,直接在这里面把最新的代码重新把代码发一遍(有个别类可能暂时没用到,可以删了,但是可能有些同学可能喜欢对项目结构,所以就全发了),如果有问题可以联系我进行修改。

        这里演示一下(因为个人比较反感收集手机号的软件,所以这里用了类似微软的安全问题,主要是是用来找回密码的,如果想换成手机号+验证码的也可以自己使用第三方API)

兔子系统注册登录演示

我知道是来看代码的,就不多扯皮了,直接上干货:

一、数据库

用户表(这里的头像这次没有用到,应该在下次才会使用):

-- 创建用户表
CREATE TABLE CUSTOMER (
  ID VARCHAR2(32) NOT NULL,
  ACCOUNT VARCHAR2(9) NOT NULL,
  ROLE VARCHAR2(1) DEFAULT '2' NOT NULL,
  HEAD_PROTRAIT_NAME VARCHAR2(200) NOT NULL,
  HEAD_PROTRAIT_FILE VARCHAR2(200) NOT NULL,
  NICK_NAME VARCHAR2(32) NOT NULL,
  PASSWORD VARCHAR2(64) NOT NULL,
  SALT VARCHAR2(32) NOT NULL,
  BIRTHDAY Date,
  AGE VARCHAR2(3),
  SEX VARCHAR(1),
  SAFETY_PROBLEM1_ID VARCHAR2(64),
  SAFETY_PROBLEM2_ID VARCHAR2(64),
  SAFETY_PROBLEM3_ID VARCHAR2(64),
  SAFETY_PROBLEM1_ANSWER VARCHAR2(32),
  SAFETY_PROBLEM2_ANSWER VARCHAR2(32),
  SAFETY_PROBLEM3_ANSWER VARCHAR2(32),
  CREATE_TIME Date DEFAULT SYSDATE NOT NULL, 
  UPDATE_TIME Date DEFAULT SYSDATE NOT NULL,
  IS_DELETE varchar(1) DEFAULT 'N' NOT NULL
);
-- 各个字段的注释
COMMENT ON TABLE CUSTOMER IS '用户表';
COMMENT ON COLUMN CUSTOMER.ID IS '用户表ID';
COMMENT ON COLUMN CUSTOMER.ACCOUNT IS '账号';
COMMENT ON COLUMN CUSTOMER.ROLE IS '角色 0 超级管理员;1 管理员;2 普通用户';
COMMENT ON COLUMN CUSTOMER.HEAD_PROTRAIT_NAME IS '头像图片名字';
COMMENT ON COLUMN CUSTOMER.HEAD_PROTRAIT_FILE IS '头像二进制文件';
COMMENT ON COLUMN CUSTOMER.NICK_NAME IS '昵称';
COMMENT ON COLUMN CUSTOMER.PASSWORD IS '密码';
COMMENT ON COLUMN CUSTOMER.SALT IS '盐加密';
COMMENT ON COLUMN CUSTOMER.BIRTHDAY IS '出生日期';
COMMENT ON COLUMN CUSTOMER.AGE IS '年龄';
COMMENT ON COLUMN CUSTOMER.SEX IS '性别 1 女;0 男;2 保密';
COMMENT ON COLUMN CUSTOMER.SAFETY_PROBLEM1_ID IS '安全问题1id';
COMMENT ON COLUMN CUSTOMER.SAFETY_PROBLEM2_ID IS '安全问题2id';
COMMENT ON COLUMN CUSTOMER.SAFETY_PROBLEM3_ID IS '安全问题3id';
COMMENT ON COLUMN CUSTOMER.SAFETY_PROBLEM1_ANSWER IS '安全问题1答案';
COMMENT ON COLUMN CUSTOMER.SAFETY_PROBLEM2_ANSWER IS '安全问题2答案';
COMMENT ON COLUMN CUSTOMER.SAFETY_PROBLEM3_ANSWER IS '安全问题3答案';
COMMENT ON COLUMN CUSTOMER.CREATE_TIME IS '创建时间';
COMMENT ON COLUMN CUSTOMER.UPDATE_TIME IS '更新时间';
COMMENT ON COLUMN CUSTOMER.IS_DELETE IS '是否删除 Y 已删除;N 未删除';

 2、安全问题表

-- 创建安全问题表
CREATE TABLE SAFETYPROBLEM (
  ID VARCHAR2(32) NOT NULL,
  PROBLEM VARCHAR2(64) NOT NULL,
  CREATE_TIME Date DEFAULT SYSDATE NOT NULL, 
  UPDATE_TIME Date DEFAULT SYSDATE NOT NULL,
  IS_DELETE varchar(1) DEFAULT 'N' NOT NULL
);

-- 各个字段的注释
COMMENT ON TABLE SAFETYPROBLEM IS '安全问题表';
COMMENT ON COLUMN SAFETYPROBLEM.ID IS '安全问题表ID';
COMMENT ON COLUMN SAFETYPROBLEM.PROBLEM IS '问题';
COMMENT ON COLUMN SAFETYPROBLEM.CREATE_TIME IS '创建时间';
COMMENT ON COLUMN SAFETYPROBLEM.UPDATE_TIME IS '更新时间';
COMMENT ON COLUMN SAFETYPROBLEM.IS_DELETE IS '是否删除 Y 已删除;N 未删除';

二、后端(下面的代码都是根据项目结构依次发出来的)

小知识:

一般用法:
GET:读取资源(查询操作),不会更改服务器上的数据。
POST:创建资源(插入操作),将数据发送到服务器进行处理。(后端代码使用了 @RequestBody 注解,它通常与 POST 请求配合使用。)
PUT:更新资源(修改操作),用提供的数据替换现有资源。
DELETE:删除资源,移除服务器上的数据。

1、结构

2、AjaxResult 

package com.rabbitSystem.carrot.common.core;

import com.rabbitSystem.carrot.common.utils.StringUtils;

import java.util.HashMap;

/**
 * 操作消息提醒
 */
public class AjaxResult extends HashMap<String, Object> {
    private static final long serialVersionUID = 1L;

    /**
     * 状态码
     */
    public static final String CODE_TAG = "code";

    /**
     * 返回内容
     */
    public static final String MSG_TAG = "msg";

    /**
     * 数据对象
     */
    public static final String DATA_TAG = "data";

    /**
     * 数据总数量
     */
    public static final String DATA_COUNT = "count";

    /**
     * 状态类型
     */
    public enum Type {
        /**
         * 成功
         */
        SUCCESS(200),
        /**
         * 警告
         */
        WARN(301),
        /**
         * 校验失败
         */
        FAILED(-1),
        /**
         * 错误
         */
        ERROR(500);
        private final int value;

        Type(int value) {
            this.value = value;
        }

        public int value() {
            return this.value;
        }
    }

    /**
     * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
     */
    public AjaxResult() {
    }

    /**
     * 初始化一个新创建的 AjaxResult 对象
     *
     * @param type 状态类型
     * @param msg  返回内容
     */
    public AjaxResult(Type type, String msg) {
        super.put(CODE_TAG, type.value);
        super.put(MSG_TAG, msg);
    }

    /**
     * 初始化一个新创建的 AjaxResult 对象
     *
     * @param type 状态类型
     * @param msg  返回内容
     * @param data 数据对象
     */
    public AjaxResult(Type type, String msg, Object data) {
        super.put(CODE_TAG, type.value);
        super.put(MSG_TAG, msg);
        if (StringUtils.isNotNull(data)) {
            super.put(DATA_TAG, data);
        }
    }

    /**
     * 初始化一个新创建的 AjaxResult 对象
     * @param type  状态类型
     * @param msg   返回内容
     * @param data  数据对象
     * @param count 数据总数量
     */
    public AjaxResult(Type type, String msg, Object data,Integer count) {
        super.put(CODE_TAG, type.value);
        super.put(MSG_TAG, msg);
        if (StringUtils.isNotNull(data)) {
            super.put(DATA_TAG, data);
        }
        if(count!=null){
            super.put(DATA_COUNT,count);
        }
    }

    /**
     * 方便链式调用
     *
     * @param key   键
     * @param value 值
     * @return 数据对象
     */
    @Override
    public AjaxResult put(String key, Object value) {
        super.put(key, value);
        return this;
    }

    /**
     * 返回成功消息
     *
     * @return 成功消息
     */
    public static AjaxResult success() {
        return AjaxResult.success("操作成功");
    }

    /**
     * 返回成功数据
     *
     * @return 成功消息
     */
    public static AjaxResult success(Object data) {
        return AjaxResult.success("操作成功", data);
    }

    /**
     * 返回成功消息
     *
     * @param msg 返回内容
     * @return 成功消息
     */
    public static AjaxResult success(String msg) {
        return AjaxResult.success(msg, null);
    }

    /**
     * 返回成功消息
     *
     * @param msg  返回内容
     * @param data 数据对象
     * @return 成功消息
     */
    public static AjaxResult success(String msg, Object data) {
        return new AjaxResult(Type.SUCCESS, msg, data);
    }

    /**
     * 返回警告消息
     *
     * @param msg 返回内容
     * @return 警告消息
     */
    public static AjaxResult warn(String msg) {
        return AjaxResult.warn(msg, null);
    }

    /**
     * 返回警告消息
     *
     * @param msg  返回内容
     * @param data 数据对象
     * @return 警告消息
     */
    public static AjaxResult warn(String msg, Object data) {
        return new AjaxResult(Type.WARN, msg, data);
    }

    /**
     * 返回错误消息
     *
     * @return
     */
    public static AjaxResult error() {
        return AjaxResult.error("操作失败");
    }

    /**
     * 返回错误消息
     *
     * @param msg 返回内容
     * @return 警告消息
     */
    public static AjaxResult error(String msg) {
        return AjaxResult.error(msg, null);
    }

    /**
     * 返回错误消息
     *
     * @param msg  返回内容
     * @param data 数据对象
     * @return 警告消息
     */
    public static AjaxResult error(String msg, Object data) {
        return new AjaxResult(Type.ERROR, msg, data);
    }
}
3、CorsConfig 
package com.rabbitSystem.carrot.common.core;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
/**
 * 解决跨域问题
 */
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*") // 允许任意域名访问,或者替换为特定的域名
                .allowedMethods("GET", "POST", "PUT", "DELETE") // 允许的请求方法
                .allowedHeaders("Content-Type", "Authorization") // 允许的请求头
                .maxAge(3600); // 可选的,设置预检请求的缓存时间
    }
}

4、PasswordEncoder 

package com.rabbitSystem.carrot.common.utils;

import java.security.MessageDigest;

/**
 * @Description: 密码加密
 */
public class PasswordEncoder {

	//返回十六进制数字字符串
	private final static String[] HEX_DIGITS = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d",
			"e", "f" };

	private final static String MD5 = "MD5";

	private Object salt;
	private String algorithm;

	public PasswordEncoder(Object salt) {
		this(salt, MD5);
	}

	public PasswordEncoder(Object salt, String algorithm) {
		this.salt = salt;
		this.algorithm = algorithm;
	}

	/**
	 * 密码加密
	 *
	 * @param rawPass
	 * @return
	 */
	public String encode(String rawPass) {
		String result = null;
		try {
			MessageDigest md = MessageDigest.getInstance(algorithm);
			// 加密后的字符串
			result = byteArrayToHexString(md.digest(mergePasswordAndSalt(rawPass).getBytes("utf-8")));
		} catch (Exception ex) {
		}
		return result;
	}

	/**
	 * 密码匹配验证
	 *
	 * @param encPass
	 *            密文
	 * @param rawPass
	 *            明文
	 * @return
	 */
	public boolean matches(String encPass, String rawPass) {
		String pass1 = "" + encPass;
		String pass2 = encode(rawPass);

		return pass1.equals(pass2);
	}

	/**
	 * 密码匹配验证
	 *
	 * @param encPass
	 *            密文
	 * @param rawPass
	 *            密文
	 * @return
	 */
	public boolean matchesNotSalt(String encPass, String rawPass) {
		String pass1 = "" + encPass;
		String pass2 = rawPass;
		return pass1.equals(pass2);
	}

	private String mergePasswordAndSalt(String password) {
		if (password == null) {
			password = "";
		}

		if ((salt == null) || "".equals(salt)) {
			return password;
		} else {
			return password + "{" + salt.toString() + "}";
		}
	}

	/**
	 * 转换字节数组为16进制字串
	 *
	 * @param b
	 *            字节数组
	 * @return 16进制字串
	 */
	private String byteArrayToHexString(byte[] b) {
		StringBuffer resultSb = new StringBuffer();
		for (int i = 0; i < b.length; i++) {
			resultSb.append(byteToHexString(b[i]));
		}
		return resultSb.toString();
	}

	/**
	 * 将字节转换为16进制
	 *
	 * @param b
	 * @return
	 */
	private static String byteToHexString(byte b) {
		int n = b;
		if (n < 0) {
			n = 256 + n;
		}
		int d1 = n / 16;
		int d2 = n % 16;
		return HEX_DIGITS[d1] + HEX_DIGITS[d2];
	}

}

5、PasswordUtils 

package com.rabbitSystem.carrot.common.utils;

import java.util.UUID;

/**
 * @Description: 密码工具类
 */
public class PasswordUtils {

	/**
	 * 匹配密码
	 * @param salt 盐
	 * @param rawPass 明文
	 * @param encPass 密文
	 * @return
	 */
	public static boolean matches(String salt, String rawPass, String encPass) {
		return new PasswordEncoder(salt).matches(encPass, rawPass);
	}

	/**
	 * 明文密码加密
	 * @param rawPass 明文
	 * @param salt
	 * @return
	 */
	public static String encode(String rawPass, String salt) {
		return new PasswordEncoder(salt).encode(rawPass);
	}

	/**
	 * 获取加密盐
	 * @return
	 */
	public static String getSalt() {
		return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 20);
	}
}

6、SnowflakeIdWorker 

package com.rabbitSystem.carrot.common.utils;


import org.apache.log4j.Logger;

import java.util.Random;

/**
 * SnowFlake算法的优点:
 *    ① 高性能高可用:生成时不依赖于数据库,完全在内存中生成。
 *    ② 容量大:每秒中能生成数百万的自增ID。
 *    ③ ID自增:存入数据库中,索引效率高。
 * SnowFlake算法的缺点:
 *      依赖与系统时间的一致性,如果系统时间被回调,或者改变,可能会造成id冲突或者重复。
 */
public class SnowflakeIdWorker {

    private Logger logger = Logger.getLogger(SnowflakeIdWorker.class);

    //保存数字0-9 和 大小写字母
    private final String STR = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    //机器ID  2进制5位  32位减掉1位 31个
    private final long WORKER_ID;

    //设置一个时间初始值    2^41 - 1   差不多可以用69年
    private final static long TWE_POCH = 1288834974657L;

    //代表一毫秒内生成的多个id的最新序号  12位 4096 -1 = 4095 个
    private long SEQUENCE = 0L;
    //5位的机器id
    private final static long WORKER_ID_BITS = 5L;

    //这个是二进制运算,就是5 bit最多只能有31个数字,也就是说机器id最多只能是32以内
    public final static long MAX_WORKER_ID = -1L ^ -1L << WORKER_ID_BITS;

    //每毫秒内产生的id数
    private final static long SEQUENCE_BITS = 10L;
    private final static long WORKER_ID_SHIFT = SEQUENCE_BITS;
    private final static long TIME_STAMPLEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
    public final static long SEQUENCE_MASK = -1L ^ -1L << SEQUENCE_BITS;

    //记录产生时间毫秒数,判断是否是同1毫秒
    private long LAST_TIMESTAMP = -1L;
    public SnowflakeIdWorker(final long workerId) {
        // 检查机器id是否超过31 不能小于0
        if (workerId > MAX_WORKER_ID || workerId < 0) {
            throw new IllegalArgumentException(String.format(
                    "worker Id can't be greater than %d or less than 0",
                    MAX_WORKER_ID));
        }
        this.WORKER_ID = workerId;
    }

    // 这个是核心方法,通过调用nextId()方法,让当前这台机器上的snowflake算法程序生成一个全局唯一的id
    public synchronized String nextId() {
        // 获取当前时间戳,单位是毫秒
        long timestamp = this.timeGen();
        if (this.LAST_TIMESTAMP == timestamp) {
            /** 一个毫秒内最多只能有4096个数字,无论你传递多少进来,
                这个位运算保证始终就是在4096这个范围内,避免你自己传递个sequence超过了4096这个范围
             */
            this.SEQUENCE = (this.SEQUENCE + 1) & SEQUENCE_MASK;
            //当某一毫秒的时间,产生的id数 超过4095,系统会进入等待,直到下一毫秒,系统继续产生ID
            if (this.SEQUENCE == 0) {
              //  logger.info("###########" + SEQUENCE_MASK);
                timestamp = this.tilNextMillis(this.LAST_TIMESTAMP);
            }
        } else {
            this.SEQUENCE = 0;
        }
        if (timestamp < this.LAST_TIMESTAMP) {
            try {
                throw new Exception(
                        String.format(
                                "Clock moved backwards. Refusing to generate id for %d milliseconds",
                                this.LAST_TIMESTAMP - timestamp));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        // 这儿记录一下最近一次生成id的时间戳,单位是毫秒
        this.LAST_TIMESTAMP = timestamp;
        // 这儿就是最核心的二进制位运算操作,生成一个64bit的id
        // 先将当前时间戳左移,放到41 bit那儿;将机房id左移放到5 bit那儿;将机器id左移放到5 bit那儿;将序号放最后12 bit
        // 最后拼接起来成一个64 bit的二进制数字,转换成10进制就是个long型
        long nextId = ((timestamp - TWE_POCH << TIME_STAMPLEFT_SHIFT))
                | (this.WORKER_ID << WORKER_ID_SHIFT) | (this.SEQUENCE);
      /*  logger.info("timestamp:" + timestamp + ",timestampLeftShift:"
                + TIME_STAMPLEFT_SHIFT + ",nextId:" + nextId + ",workerId:"
                + WORKER_ID + ",sequence:" + SEQUENCE);*/
        return randomNextId(nextId);
    }

    /**
     * 当某一毫秒的时间,产生的id数 超过4095,系统会进入等待,直到下一毫秒,系统继续产生ID
     * @param lastTimestamp
     * @return
     */
    private long tilNextMillis(final long lastTimestamp) {
        long timestamp = this.timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = this.timeGen();
        }
        return timestamp;
    }

    //获取当前时间戳
    private long timeGen() {
        return System.currentTimeMillis();
    }

    private String randomNextId(Long nextId){
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < 16; i++) {
            //创建一个新的随机数生成器
            Random random = new Random();
            //返回[0,string.length)范围的int值    作用:保存下标
            int index = random.nextInt(STR.length());
            //charAt() : 返回指定索引处的 char 值   ==》赋值给char字符对象ch
            char ch = STR.charAt(index);
            char id = String.valueOf(nextId).charAt(i);
            sb.append(id);
            // append(char c) :将 char 参数的字符串表示形式追加到此序列  ==》即将每次获取的ch值作拼接
            sb.append(ch);
        }
        return sb.toString();
    }

    /**
     * @Description: 获取唯一id
     * @param workerId 工作id
     */
    public static String getId(String workerId){
        if(workerId == null && "".equals(workerId)){
            workerId = "2";
        }
        SnowflakeIdWorker worker = new SnowflakeIdWorker(Long.valueOf(workerId));
        return worker.nextId();
    }

    /**
     * %0是在num转化为字符后,长度达不到length的时候,前面以0补足。
     * d是个占位符,会被参数num所替换。
     *
     * @param num       传递的数字
     * @param length   参数length代表的是格式化后字符串的总长度
     * @return
     */
    public static String leftFormatInteger(int num, int length) {
        //String.format用来格式化字符串(按指定的规则连接字符串或输出其它变量并返回新的字符串)
        return String.format("%0"+length+"d", num);
    }

    public static void main(String[] args){
        System.out.println(leftFormatInteger(3,9) + SnowflakeIdWorker.getId("0008"));
        String id = SnowflakeIdWorker.getId("000001");
        System.out.println(id +"\t"  + id.length());
        System.out.println("000051l2f9R8n3X8j6j9k13808L7Z0y312a3d".length());


    }
}

7、StringUtils 

package com.rabbitSystem.carrot.common.utils;


import java.util.Collection;
import java.util.Map;

/**
 * 字符串工具类
 */
public class StringUtils extends org.apache.commons.lang3.StringUtils {
    /**
     * 空字符串
     */
    private static final String NULLSTR = "";

    /**
     * 下划线
     */
    private static final char SEPARATOR = '_';

    /**
     * 获取参数不为空值
     *
     * @param value defaultValue 要判断的value
     * @return value 返回值
     */
    public static <T> T nvl(T value, T defaultValue) {
        return value != null ? value : defaultValue;
    }

    /**
     * * 判断一个Collection是否为空, 包含List,Set,Queue
     *
     * @param coll 要判断的Collection
     * @return true:为空 false:非空
     */
    public static boolean isEmpty(Collection<?> coll) {
        return isNull(coll) || coll.isEmpty();
    }

    /**
     * * 判断一个Collection是否非空,包含List,Set,Queue
     *
     * @param coll 要判断的Collection
     * @return true:非空 false:空
     */
    public static boolean isNotEmpty(Collection<?> coll) {
        return !isEmpty(coll);
    }

    /**
     * * 判断一个对象数组是否为空
     *
     * @param objects 要判断的对象数组
     *                * @return true:为空 false:非空
     */
    public static boolean isEmpty(Object[] objects) {
        return isNull(objects) || (objects.length == 0);
    }

    /**
     * * 判断一个对象数组是否非空
     *
     * @param objects 要判断的对象数组
     * @return true:非空 false:空
     */
    public static boolean isNotEmpty(Object[] objects) {
        return !isEmpty(objects);
    }

    /**
     * * 判断一个Map是否为空
     *
     * @param map 要判断的Map
     * @return true:为空 false:非空
     */
    public static boolean isEmpty(Map<?, ?> map) {
        return isNull(map) || map.isEmpty();
    }

    /**
     * * 判断一个Map是否为空
     *
     * @param map 要判断的Map
     * @return true:非空 false:空
     */
    public static boolean isNotEmpty(Map<?, ?> map) {
        return !isEmpty(map);
    }

    /**
     * * 判断一个字符串是否为空串
     *
     * @param str String
     * @return true:为空 false:非空
     */
    public static boolean isEmpty(String str) {
        return isNull(str) || NULLSTR.equals(str.trim());
    }

    /**
     * * 判断一个字符串是否为非空串
     *
     * @param str String
     * @return true:非空串 false:空串
     */
    public static boolean isNotEmpty(String str) {
        return !isEmpty(str);
    }

    /**
     * * 判断一个对象是否为空
     *
     * @param object Object
     * @return true:为空 false:非空
     */
    public static boolean isNull(Object object) {
        return object == null;
    }

    /**
     * * 判断一个对象是否非空
     *
     * @param object Object
     * @return true:非空 false:空
     */
    public static boolean isNotNull(Object object) {
        return !isNull(object);
    }

    /**
     * * 判断一个对象是否是数组类型(Java基本型别的数组)
     *
     * @param object 对象
     * @return true:是数组 false:不是数组
     */
    public static boolean isArray(Object object) {
        return isNotNull(object) && object.getClass().isArray();
    }

    /**
     * 去空格
     */
    public static String trim(String str) {
        return (str == null ? "" : str.trim());
    }

    /**
     * 截取字符串
     *
     * @param str   字符串
     * @param start 开始
     * @return 结果
     */
    public static String substring(final String str, int start) {
        if (str == null) {
            return NULLSTR;
        }

        if (start < 0) {
            start = str.length() + start;
        }

        if (start < 0) {
            start = 0;
        }
        if (start > str.length()) {
            return NULLSTR;
        }

        return str.substring(start);
    }

    /**
     * 截取字符串
     *
     * @param str   字符串
     * @param start 开始
     * @param end   结束
     * @return 结果
     */
    public static String substring(final String str, int start, int end) {
        if (str == null) {
            return NULLSTR;
        }

        if (end < 0) {
            end = str.length() + end;
        }
        if (start < 0) {
            start = str.length() + start;
        }

        if (end > str.length()) {
            end = str.length();
        }

        if (start > end) {
            return NULLSTR;
        }

        if (start < 0) {
            start = 0;
        }
        if (end < 0) {
            end = 0;
        }

        return str.substring(start, end);
    }

    /**
     * 格式化文本, {} 表示占位符<br>
     * 此方法只是简单将占位符 {} 按照顺序替换为参数<br>
     * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可<br>
     * 例:<br>
     * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b<br>
     * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a<br>
     * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b<br>
     *
     * @param template 文本模板,被替换的部分用 {} 表示
     * @param params   参数值
     * @return 格式化后的文本
     */
   /* public static String format(String template, Object... params) {
        if (isEmpty(params) || isEmpty(template)) {
            return template;
        }
        return StrFormatter.format(template, params);
    }*/

    /**
     * 下划线转驼峰命名
     */
    public static String toUnderScoreCase(String str) {
        if (str == null) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        // 前置字符是否大写
        boolean preCharIsUpperCase = true;
        // 当前字符是否大写
        boolean curreCharIsUpperCase = true;
        // 下一字符是否大写
        boolean nexteCharIsUpperCase = true;
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            if (i > 0) {
                preCharIsUpperCase = Character.isUpperCase(str.charAt(i - 1));
            } else {
                preCharIsUpperCase = false;
            }

            curreCharIsUpperCase = Character.isUpperCase(c);

            if (i < (str.length() - 1)) {
                nexteCharIsUpperCase = Character.isUpperCase(str.charAt(i + 1));
            }

            if (preCharIsUpperCase && curreCharIsUpperCase && !nexteCharIsUpperCase) {
                sb.append(SEPARATOR);
            } else if ((i != 0 && !preCharIsUpperCase) && curreCharIsUpperCase) {
                sb.append(SEPARATOR);
            }
            sb.append(Character.toLowerCase(c));
        }
        return sb.toString();
    }

    /**
     * 是否包含字符串
     *
     * @param str  验证字符串
     * @param strs 字符串组
     * @return 包含返回true
     */
    public static boolean inStringIgnoreCase(String str, String... strs) {
        if (str != null && strs != null) {
            for (String s : strs) {
                if (str.equalsIgnoreCase(trim(s))) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 例如:HELLO_WORLD->HelloWorld
     *
     * @param name 转换前的下划线大写方式命名的字符串
     * @return 转换后的驼峰式命名的字符串
     */
    public static String convertToCamelCase(String name) {
        StringBuilder result = new StringBuilder();
        // 快速检查
        if (name == null || name.isEmpty()) {
            // 没必要转换
            return "";
        } else if (!name.contains("_")) {
            // 不含下划线,仅将首字母大写
            return name.substring(0, 1).toUpperCase() + name.substring(1);
        }
        // 用下划线将原始字符串分割
        String[] camels = name.split("_");
        for (String camel : camels) {
            // 跳过原始字符串中开头、结尾的下换线或双重下划线
            if (camel.isEmpty()) {
                continue;
            }
            // 首字母大写
            result.append(camel.substring(0, 1).toUpperCase());
            result.append(camel.substring(1).toLowerCase());
        }
        return result.toString();
    }

    /**
     * 驼峰式命名法
     * 例如:user_name->userName
     */
    public static String toCamelCase(String s) {
        if (s == null) {
            return null;
        }
        if (s.indexOf(SEPARATOR) == -1) {
            return s;
        }
        s = s.toLowerCase();
        StringBuilder sb = new StringBuilder(s.length());
        boolean upperCase = false;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);

            if (c == SEPARATOR) {
                upperCase = true;
            } else if (upperCase) {
                sb.append(Character.toUpperCase(c));
                upperCase = false;
            } else {
                sb.append(c);
            }
        }
        return sb.toString();
    }

    @SuppressWarnings("unchecked")
    public static <T> T cast(Object obj) {
        return (T) obj;
    }
}

8、CustomerController

package com.rabbitSystem.carrot.controller;

import com.rabbitSystem.carrot.common.core.AjaxResult;
import com.rabbitSystem.carrot.pojo.sel.CustomerSelPojo;
import com.rabbitSystem.carrot.service.CustomerService;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.List;

@RestController
@RequestMapping("/CustomerController")
/**
 * 用户的控制层
 */
public class CustomerController {

    /**
     * 调用业务访问层对象
     */
    @Resource
    private CustomerService customerService;

    /**
     * GetMapping:处理 GET 请求(查询一般用 get 请求)
     * 查询所有用户信息的方法
     */
    @GetMapping("/selCustomers")
    public AjaxResult selCustomers(){
        List<CustomerSelPojo> customerSelPojoList = customerService.selCustomers();
        int i = 200;
        if (customerSelPojoList == null){
            i = 0;
        };
        String msg = i == 200 ?  "查询成功!" : "查询失败!";
        return new AjaxResult(i == 200 ? AjaxResult.Type.SUCCESS : AjaxResult.Type.ERROR,msg, customerSelPojoList);
    }
}

9、LoginAndRegisterController

package com.rabbitSystem.carrot.controller;

import com.rabbitSystem.carrot.common.core.AjaxResult;
import com.rabbitSystem.carrot.pojo.sel.CustomerSelPojo;
import com.rabbitSystem.carrot.service.LoginAndRegisterService;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

/**
 * 标识控制层
 * @RestController 组合注解, @Controller +  @ResponseBody ,
 * 在类头部指定@RestController注解,就表示该控制器下所有的方法均以 json格式类型从服务端响应给客户端(前台)
 */
@RestController
/**
 * 处理 HTTP 请求
 */
@RequestMapping("/LoginAndRegisterController")
/**
 * 注册登录控制层
 */
public class LoginAndRegisterController {

    @Resource
    private LoginAndRegisterService loginAndRegisterService;

    /**
     * PostMapping:处理 Post 请求(插入一般用 post 请求)
     * 用户注册的方法
     */
    @PostMapping("/register")
    public AjaxResult register(@RequestBody CustomerSelPojo customerSelPojo){
        customerSelPojo = loginAndRegisterService.register(customerSelPojo);
        String msg;
        if (customerSelPojo.getI()>0){
            msg = "注册成功,您的账号为:"+ customerSelPojo.getAccount();
        }else {
            msg = "注册失败,请重试!";
        }
        return new AjaxResult(customerSelPojo.getI() > 0 ? AjaxResult.Type.SUCCESS : AjaxResult.Type.ERROR,msg, customerSelPojo);
    }

    /**
     * 登录
     * @param customerSelPojo
     * @return
     */
    @PostMapping("/login")
    public AjaxResult login(@RequestBody CustomerSelPojo customerSelPojo){
        customerSelPojo = loginAndRegisterService.login(customerSelPojo);
        String msg;
        if (customerSelPojo.getI()== 0){
            msg = "当前账号不存在!";
        }else if (customerSelPojo.getI()== 1){
            msg = "登录成功!";
        }else {
            msg = "密码错误!";
        }
        return new AjaxResult(customerSelPojo.getI() == 1 ? AjaxResult.Type.SUCCESS : AjaxResult.Type.ERROR,msg, customerSelPojo);
    }
}

10、RetrievePasswordController

package com.rabbitSystem.carrot.controller;

import com.rabbitSystem.carrot.common.core.AjaxResult;
import com.rabbitSystem.carrot.pojo.sel.CustomerSelPojo;
import com.rabbitSystem.carrot.service.CustomerService;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

@RestController
@RequestMapping("/RetrievePasswordController")
public class RetrievePasswordController {

    @Resource
    private CustomerService customerService;

    /**
     * 找回密码校验账号和安全问题
     * @param customerSelPojo
     * @return
     */
    @PostMapping("/retrievePassword")
    public AjaxResult selCustomers(@RequestBody CustomerSelPojo customerSelPojo){
        String safetyProblem1Id = customerSelPojo.getSafetyProblem1Id();
        String safetyProblem2Id = customerSelPojo.getSafetyProblem2Id();
        String safetyProblem3Id = customerSelPojo.getSafetyProblem3Id();
        String safetyProblem1Answer = customerSelPojo.getSafetyProblem1Answer();
        String safetyProblem2Answer = customerSelPojo.getSafetyProblem2Answer();
        String safetyProblem3Answer = customerSelPojo.getSafetyProblem3Answer();

        customerSelPojo = customerService.selCustomer(customerSelPojo.getAccount());
        if (customerSelPojo == null){
            return new AjaxResult(AjaxResult.Type.ERROR,"该账号不存在!");
        }else if (safetyProblem1Id.equals(customerSelPojo.getSafetyProblem1Id())
                && safetyProblem2Id.equals(customerSelPojo.getSafetyProblem2Id())
                && safetyProblem3Id.equals(customerSelPojo.getSafetyProblem3Id())
                && safetyProblem1Answer.equals(customerSelPojo.getSafetyProblem1Answer())
                && safetyProblem2Answer.equals(customerSelPojo.getSafetyProblem2Answer())
                && safetyProblem3Answer.equals(customerSelPojo.getSafetyProblem3Answer())){
            return new AjaxResult(AjaxResult.Type.SUCCESS,"查询成功!", customerSelPojo);
        }else {
            return new AjaxResult(AjaxResult.Type.ERROR,"您选择的问题及答案不正确!");
        }
    }


    /**
     * 根据账号修改密码
     * @param customerSelPojo
     * @return
     */
    @PostMapping("/updPassword")
    public AjaxResult updPassword(@RequestBody CustomerSelPojo customerSelPojo){
        int i = customerService.updPassword(customerSelPojo);
        String msg = "";
        if (i==1){
            msg = "修改成功";
        }else {
            msg = "修改失败";
        }
        return new AjaxResult( i == 1 ? AjaxResult.Type.SUCCESS : AjaxResult.Type.ERROR,msg);
    }
}

11、SafetyProblemController

package com.rabbitSystem.carrot.controller;

import com.rabbitSystem.carrot.common.core.AjaxResult;
import com.rabbitSystem.carrot.pojo.SafetyProblem;
import com.rabbitSystem.carrot.service.SafetyProblemService;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.List;

@RestController
@RequestMapping("/SafetyProblemController")
public class SafetyProblemController {

    @Resource
    private SafetyProblemService safetyProblemService;

    /**
     * PostMapping:处理 Post 请求(插入一般用 post 请求)
     * 用户注册的方法
     */
    @GetMapping("/selSafetyProblems")
    public AjaxResult selSafetyProblems(){
        List<SafetyProblem> safetyProblemList = safetyProblemService.selSafetyProblems();
        int i = 0;
        if (safetyProblemList != null){
            i = 1;
        }
        String msg = i == 0 ? "查询失败" : "查询成功";
        return new AjaxResult(i > 0 ? AjaxResult.Type.SUCCESS : AjaxResult.Type.ERROR,msg, safetyProblemList);
    }
}

12、CustomerMapper

package com.rabbitSystem.carrot.mapper;

import com.rabbitSystem.carrot.pojo.sel.CustomerSelPojo;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface CustomerMapper {
     List<CustomerSelPojo> selCustomers();

    CustomerSelPojo selCustomer(String account);

    int updPassword(CustomerSelPojo customerSelPojo);
}

13、LoginAndRegisterMapper

package com.rabbitSystem.carrot.mapper;

import com.rabbitSystem.carrot.pojo.sel.CustomerSelPojo;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface LoginAndRegisterMapper {

    int register(CustomerSelPojo customerSelPojo);
}

14、SafetyProblemMapper

package com.rabbitSystem.carrot.mapper;

import com.rabbitSystem.carrot.pojo.SafetyProblem;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface SafetyProblemMapper {
    List<SafetyProblem> selSafetyProblems();
}

15、CustomerSelPojo

package com.rabbitSystem.carrot.pojo.sel;

import lombok.Data;

@Data
public class CustomerSelPojo {

  // 用户信息
  private String id;
  private String account;
  private String role;
  private String headProtraitName;
  private String headProtraitFile;
  private String nickName;
  private String password;
  private String salt;
  private String birthday;
  private String age;
  private String sex;
  private String safetyProblem1Id;
  private String safetyProblem2Id;
  private String safetyProblem3Id;
  private String safetyProblem1Answer;
  private String safetyProblem2Answer;
  private String safetyProblem3Answer;
  private String createTime;
  private String updateTime;
  private String isDelete;

  // 功能
  private int i; // 注册时新增个数

}

16、SafetyProblemPojo

package com.rabbitSystem.carrot.pojo.sel;

import lombok.Data;

@Data
public class SafetyProblemPojo {

    private String id;
    private String problem;
    private String createTime;
    private String updateTime;
    private String isDelete;
    private int i;
}

17、Customer

package com.rabbitSystem.carrot.pojo;

import lombok.Data;

@Data
public class Customer {

  // 用户信息
  private String id;
  private String account;
  private String role;
  private String headProtraitName;
  private String headProtraitFile;
  private String nickName;
  private String password;
  private String salt;
  private String birthday;
  private String age;
  private String sex;
  private String safetyProblem1Id;
  private String safetyProblem2Id;
  private String safetyProblem3Id;
  private String safetyProblem1Answer;
  private String safetyProblem2Answer;
  private String safetyProblem3Answer;
  private String createTime;
  private String updateTime;
  private String isDelete;

}

18、SafetyProblem

package com.rabbitSystem.carrot.pojo;

import lombok.Data;

@Data
public class SafetyProblem {

  private String id;
  private String problem;
  private String createTime;
  private String updateTime;
  private String isDelete;

}

19、CustomerImpl

package com.rabbitSystem.carrot.service.impl;

import com.rabbitSystem.carrot.common.utils.PasswordUtils;
import com.rabbitSystem.carrot.mapper.CustomerMapper;
import com.rabbitSystem.carrot.pojo.sel.CustomerSelPojo;
import com.rabbitSystem.carrot.service.CustomerService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

@Service
public class CustomerImpl implements CustomerService {

    //创建数据访问层对象
    @Resource
    private CustomerMapper customerMapper;

    @Override
    public List<CustomerSelPojo> selCustomers() {
        List<CustomerSelPojo>  customerSelPojo= customerMapper.selCustomers();
        return customerSelPojo;
    }

    @Override
    public CustomerSelPojo selCustomer(String account) {
        CustomerSelPojo  customerSelPojo= customerMapper.selCustomer(account);
        return customerSelPojo;
    }

    @Override
    public int updPassword(CustomerSelPojo customerSelPojo) {
        String password = customerSelPojo.getPassword();
        // 密码加密
        String salt = PasswordUtils.getSalt();
        String encPass = PasswordUtils.encode(password, salt);
        customerSelPojo.setPassword(encPass);
        customerSelPojo.setSalt(salt);
        int  i = customerMapper.updPassword(customerSelPojo);
        return i;
    }
}

20、LoginAndRegisterImpl

package com.rabbitSystem.carrot.service.impl;

import com.rabbitSystem.carrot.common.utils.PasswordUtils;
import com.rabbitSystem.carrot.common.utils.SnowflakeIdWorker;
import com.rabbitSystem.carrot.mapper.LoginAndRegisterMapper;
import com.rabbitSystem.carrot.pojo.sel.CustomerSelPojo;
import com.rabbitSystem.carrot.service.CustomerService;
import com.rabbitSystem.carrot.service.LoginAndRegisterService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Random;

@Service
public class LoginAndRegisterImpl implements LoginAndRegisterService {

    @Resource
    private CustomerService customerService;

    @Resource
    private LoginAndRegisterMapper loginAndRegisterMapper;
    @Override
    public CustomerSelPojo register(CustomerSelPojo customerSelPojo) {
        // 生成用户id
        String userId = SnowflakeIdWorker.getId("00001");
        customerSelPojo.setId(userId);
        // 密码加密
        String salt = PasswordUtils.getSalt();
        String encPass = PasswordUtils.encode(customerSelPojo.getPassword(), salt);
        customerSelPojo.setPassword(encPass);
        customerSelPojo.setSalt(salt);
        // 生成账号
        Random random = new Random();
        String account;
        do {
            int randomNumber = random.nextInt(1000000000);
            account = String.format("%09d", randomNumber);
        } while (customerService.selCustomer(account) != null);
        customerSelPojo.setAccount(account);

        // 临时
        customerSelPojo.setHeadProtraitName("图片名字");
        customerSelPojo.setHeadProtraitFile("图片二进制");

        int i = loginAndRegisterMapper.register(customerSelPojo);
        customerSelPojo.setI(i);
        return customerSelPojo;
    }

    @Override
    public CustomerSelPojo login(CustomerSelPojo customerSelPojo) {
        String account = customerSelPojo.getAccount();
        String password = customerSelPojo.getPassword();
        CustomerSelPojo primitiveUser = customerService.selCustomer(account);
        if (primitiveUser == null) {
            customerSelPojo.setI(0);
        } else {
            /**
             * 根据查找用户名返回的盐和密码,跟前端传回来的进行校验
             */
            if (PasswordUtils.matches(primitiveUser.getSalt(), password, primitiveUser.getPassword())) {
                customerSelPojo.setI(1);
            }else{
                customerSelPojo.setI(2);
            }
        }
        return customerSelPojo;
    }
}

21、SafetyProblemImpl

package com.rabbitSystem.carrot.service.impl;

import com.rabbitSystem.carrot.mapper.SafetyProblemMapper;
import com.rabbitSystem.carrot.pojo.SafetyProblem;
import com.rabbitSystem.carrot.service.SafetyProblemService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

@Service
public class SafetyProblemImpl implements SafetyProblemService {

    @Resource
    private SafetyProblemMapper safetyProblemMapper;
    @Override
    public List<SafetyProblem> selSafetyProblems() {
        List<SafetyProblem>  safetyProblemList= safetyProblemMapper.selSafetyProblems();
        return safetyProblemList;
    }
}

22、CustomerService

package com.rabbitSystem.carrot.service;

import com.rabbitSystem.carrot.pojo.sel.CustomerSelPojo;

import java.util.List;

public interface CustomerService {

    /**
     * 查询所有用户信息
     * @return
     */
    List<CustomerSelPojo> selCustomers();

    /**
     * 根据账号查询个人用户信息
     * @param account
     * @return
     */
    CustomerSelPojo selCustomer(String account);

    /**
     * 根据账号修改密码
     * @param customerSelPojo
     * @return
     */
    int updPassword(CustomerSelPojo customerSelPojo);
}

23、LoginAndRegisterService

package com.rabbitSystem.carrot.service;

import com.rabbitSystem.carrot.pojo.sel.CustomerSelPojo;

public interface LoginAndRegisterService {
    CustomerSelPojo register(CustomerSelPojo customerSelPojo);

    CustomerSelPojo login(CustomerSelPojo customerSelPojo);
}

24、SafetyProblemService

package com.rabbitSystem.carrot.service;

import com.rabbitSystem.carrot.pojo.SafetyProblem;

import java.util.List;

public interface SafetyProblemService {
    /**
     * 查序所有的安全问题
     * @return
     */
    List<SafetyProblem> selSafetyProblems();
}

25、CustomerMapper.xml

<?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.rabbitSystem.carrot.mapper.CustomerMapper">

    <resultMap id="customerResultMap" type="com.rabbitSystem.carrot.pojo.sel.CustomerSelPojo">
        <id property="id" column="ID"/>
        <result property="account" column="ACCOUNT"/>
        <result property="role" column="ROLE"/>
        <result property="headProtraitName" column="HEAD_PROTRAIT_NAME"/>
        <result property="headProtraitFile" column="HEAD_PROTRAIT_FILE"/>
        <result property="nickName" column="NICK_NAME"/>
        <result property="password" column="PASSWORD"/>
        <result property="salt" column="SALT"/>
        <result property="birthday" column="BIRTHDAY"/>
        <result property="age" column="AGE"/>
        <result property="sex" column="SEX"/>
        <result property="safetyProblem1Id" column="SAFETY_PROBLEM1_ID"/>
        <result property="safetyProblem2Id" column="SAFETY_PROBLEM2_ID"/>
        <result property="safetyProblem3Id" column="SAFETY_PROBLEM3_ID"/>
        <result property="safetyProblem1Answer" column="SAFETY_PROBLEM1_ANSWER"/>
        <result property="safetyProblem2Answer" column="SAFETY_PROBLEM2_ANSWER"/>
        <result property="safetyProblem3Answer" column="SAFETY_PROBLEM3_ANSWER"/>
        <result property="createTime" column="CREATE_TIME"/>
        <result property="updateTime" column="UPDATE_TIME"/>
        <result property="isDelete" column="IS_DELETE"/>
    </resultMap>

    <select id="selCustomers" resultMap="customerResultMap">
        select T.ID,T.ACCOUNT,T.ROLE,T.HEAD_PROTRAIT_NAME,
               T.HEAD_PROTRAIT_FILE,T.NICK_NAME,T.PASSWORD,T.SALT,TO_CHAR(T.BIRTHDAY, 'YYYY-MM-DD') AS BIRTHDAY,
               T.AGE,T.SEX,T.SAFETY_PROBLEM1_ID,T.SAFETY_PROBLEM2_ID,T.SAFETY_PROBLEM3_ID,
               T.SAFETY_PROBLEM1_ANSWER,T.SAFETY_PROBLEM2_ANSWER,T.SAFETY_PROBLEM3_ANSWER,
               T.CREATE_TIME,T.UPDATE_TIME,T.IS_DELETE from CUSTOMER T
    </select>

    <select id="selCustomer" resultType="com.rabbitSystem.carrot.pojo.sel.CustomerSelPojo">
        select T.ID id,T.ACCOUNT account,T.ROLE role,T.HEAD_PROTRAIT_NAME headProtraitName,
               T.HEAD_PROTRAIT_FILE headProtraitFile,T.NICK_NAME nickName,T.PASSWORD password,T.SALT salt,
               TO_CHAR(T.BIRTHDAY, 'YYYY-MM-DD') birthday,T.AGE age,T.SEX sex,T.SAFETY_PROBLEM1_ID safetyProblem1Id,
               T.SAFETY_PROBLEM2_ID safetyProblem2Id,T.SAFETY_PROBLEM3_ID safetyProblem3Id,
               T.SAFETY_PROBLEM1_ANSWER safetyProblem1Answer,T.SAFETY_PROBLEM2_ANSWER safetyProblem2Answer,
               T.SAFETY_PROBLEM3_ANSWER safetyProblem3Answer,
               T.CREATE_TIME createTime,T.UPDATE_TIME updateTime,T.IS_DELETE isDelete
        from CUSTOMER T
        where T.ACCOUNT = #{ACCOUNT}
    </select>

    <update id="updPassword">
        UPDATE CUSTOMER T
        SET T.PASSWORD = #{password},T.SALT = #{salt}
        WHERE T.ACCOUNT = #{account}
    </update>
</mapper>

26、LoginAndRegisterMapper.xml

<?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.rabbitSystem.carrot.mapper.LoginAndRegisterMapper">

    <insert id="register">
        INSERT INTO CUSTOMER T
            (T.ID,T.ACCOUNT,T.HEAD_PROTRAIT_NAME,
               T.HEAD_PROTRAIT_FILE,T.NICK_NAME,T.PASSWORD,T.SALT,T.BIRTHDAY,
               T.AGE,T.SEX,T.SAFETY_PROBLEM1_ID,T.SAFETY_PROBLEM2_ID,T.SAFETY_PROBLEM3_ID,
               T.SAFETY_PROBLEM1_ANSWER,T.SAFETY_PROBLEM2_ANSWER,T.SAFETY_PROBLEM3_ANSWER)
        VALUES (#{id},#{account},#{headProtraitName},#{headProtraitFile},#{nickName},#{password},#{salt},
            TO_TIMESTAMP(#{birthday}, 'YYYY-MM-DD"T"HH24:MI:SS.FF3"Z"'),
            #{age},#{sex},#{safetyProblem1Id},#{safetyProblem2Id},#{safetyProblem3Id},
            #{safetyProblem1Answer},#{safetyProblem2Answer},#{safetyProblem3Answer})
    </insert>

</mapper>

27、SafetyProblemMapper.xml

<?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.rabbitSystem.carrot.mapper.SafetyProblemMapper">

    <resultMap id="customerResultMap" type="com.rabbitSystem.carrot.pojo.sel.SafetyProblemPojo">
        <id property="id" column="ID"/>
        <result property="problem" column="PROBLEM"/>
        <result property="problem" column="ROLE"/>
        <result property="createTime" column="CREATE_TIME"/>
        <result property="updateTime" column="UPDATE_TIME"/>
        <result property="isDelete" column="IS_DELETE"/>
    </resultMap>

    <select id="selSafetyProblems" resultMap="customerResultMap">
        SELECT T.ID,T.PROBLEM,T.CREATE_TIME,T.UPDATE_TIME,T.IS_DELETE
        FROM SAFETYPROBLEM T
    </select>

</mapper>

28、application.yml

## 指定web容器访问端口号
rabbit:
  name: 兔子系统
  version: v-1.0
## web容器端口号
server:
  port: 8081
## 配置数据库连接
spring:
  datasource:
    driver-class-name: oracle.jdbc.OracleDriver
    url: jdbc:oracle:thin:@//localhost:1521/ORCL
    username: 用户名
    password: 密码
## 配置mybatis中mapper.xml文件扫描
mybatis:
  type-aliases-package: com.rabbitSystem.carrot.pojo.sel.*
  mapper-locations: classpath:mapper/*.xml   # mapper.xml文件映射

三、前端(下面的代码都是根据项目结构依次发出来的)

安装中文包 npm install element-plus
安装element ui图标:Install '@element-plus/icons-vue

 小知识:

在 Vue 3 中,setup() 函数和 data() 函数分别用于不同的 API 风格。setup() 是 Composition API 的一部分,而 data() 是 Options API 的一部分。

Composition API和Options API有什么区别:
Options API:适合小型应用和初学者,结构清晰,但在复杂应用中可能会显得不够灵活和可维护。
Composition API:提供更强的逻辑复用能力,特别适合复杂应用和与 TypeScript 配合使用。当需要组织复杂逻辑时,更加灵活与便捷(我这里用的这种)。

两种代码对比:
在 Options API 中:
export default {
  data() {
    return {
      count: 0,
    };
  },
  methods: {
    increment() {
      this.count++; // 访问 data 中的 count
    },
    greet() {
      return `Hello! Count is ${this.count}`;
    },
  },
};

在 Composition API 中
import { ref } from 'vue';

export default {
  setup() {
    const count = ref(0);

    const increment = () => {
      count.value++; // 访问 count 的响应式值
    };

    const greet = () => {
      return `Hello! Count is ${count.value}`;
    };

    return {
      count,
      increment,
      greet,
    };
  },
};
如果创建对象的时候用了 ref/reactive 那么就会变成响应式对象(Proxy),想变成原数据,就需要转换
rawQuestions = toRaw(questions); // 用 toRaw 将 Proxy 对象转换成原始数据(后端传过来的),这里的 rawQuestions 是普通数组

有时候控制台打印的数据跟后端返回的数据不一致的时候,就可以看看是否是这种情况

1、结构

2、index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>兔子系统</title>
</head>
<body>
    <div id="app" style="margin: 0;padding: 0;width: 100%;height: 100vh;"></div>
</body>
<style>
    body{
        margin: 0;
        padding: 0;
        width: 100%;
        height: 100vh;
    }
</style>
</html>

3、customer.js

import axios from 'axios'; // 导入 axios 库,用于发起 HTTP 请求

// 创建 axios 实例
const instance = axios.create({
    baseURL: 'http://localhost:8081', // 设置请求的基础 URL
    timeout: 10000, // 请求超时时间(毫秒)
});

// 定义一个异步函数,用于获取客户数据
export async function selCustomersJs() {
    try {
        // 发起 GET 请求,获取客户数据
        const response = await instance.get('/CustomerController/selCustomers', {
            headers: {
                'Content-Type': 'application/json', // 设置请求头的内容类型为 JSON
            }
        });
        // 返回响应的数据
        return response;
    } catch (error) {
        // 捕获并处理请求错误
        console.error('Error fetching customers:', error); // 打印错误信息到控制台
        throw error; // 重新抛出错误,以便调用者可以进一步处理
    }
}

4、registerAndLogin.js

import axios from 'axios';

// 创建 axios 实例
const instance = axios.create({
        baseURL: 'http://localhost:8081', // 设置请求的基础 URL
        timeout: 10000, // 请求超时时间(毫秒)
});
// 使用 Axios 发送请求后,你会得到一个 Promise 对象。这个对象的 .then 方法可以用来处理请求的结果。当你收到响应数据时,它会被封装在 response 对象中。你可以从这个对象中提取你需要的数据。
// 定义一个异步函数,用于注册
export async function registerJS(formData) {
        try {
                // 发起 POST 请求,进行注册
                const response = await instance.post('/LoginAndRegisterController/register', formData);

                // 提取并返回响应中的数据
                if (response.status === 200) {
                        return response.data; // 返回 '123456' 或其他响应数据
                } else {
                        throw new Error(`Unexpected status code: ${response.status}`);
                }
        } catch (error) {
                // 捕获并处理请求错误
                console.error('Error fetching register:', error.message); // 打印错误信息到控制台
                throw error; // 重新抛出错误,以便调用者可以进一步处理
        }
}
// 登录
export async function loginJS(formData) {
        try {
                // 发起 POST 请求,进行注册
                const response = await instance.post('/LoginAndRegisterController/login', formData);

                // 提取并返回响应中的数据
                if (response.status === 200) {
                        return response.data; // 返回 '123456' 或其他响应数据
                } else {
                        throw new Error(`Unexpected status code: ${response.status}`);
                }
        } catch (error) {
                // 捕获并处理请求错误
                console.error('Error fetching register:', error.message); // 打印错误信息到控制台
                throw error; // 重新抛出错误,以便调用者可以进一步处理
        }
}

// 查询安全问题
export async function selSafetyProblemsJS() {
        try {
                const response = await instance.get('/SafetyProblemController/selSafetyProblems', {
                        headers: {
                                'Content-Type': 'application/json', // 设置请求头的内容类型为 JSON
                        }
                });
                return response; // // 假设返回的数据格式是 [{ command: 'problem1', label: '问题1、你的母亲姓什么?' }, ...]
        } catch (error) {
                // 捕获并处理请求错误
                console.error('Error fetching register:', error.message); // 打印错误信息到控制台
                throw error; // 重新抛出错误,以便调用者可以进一步处理
        }
}

5、retrievePassword.js

import axios from 'axios';

// 创建 axios 实例
const instance = axios.create({
    baseURL: 'http://localhost:8081', // 设置请求的基础 URL
    timeout: 10000, // 请求超时时间(毫秒)
});
// 定义一个异步函数,校验输入的账号和安全问题是否正确
export async function retrievePasswordJs(formData) {
    try {
        // 发起 POST 请求,进行注册
        const response = await instance.post('/RetrievePasswordController/retrievePassword', formData);

        // 提取并返回响应中的数据
        if (response.status === 200) {
            return response.data; // 返回 '123456' 或其他响应数据
        } else {
            throw new Error(`Unexpected status code: ${response.status}`);
        }
    } catch (error) {
        // 捕获并处理请求错误
        console.error('Error fetching register:', error.message); // 打印错误信息到控制台
        throw error; // 重新抛出错误,以便调用者可以进一步处理
    }
}

//修改密码
export async function updPasswordJS(formData) {
    try {
        // 发起 POST 请求,进行注册
        const response = await instance.post('/RetrievePasswordController/updPassword', formData);

        // 提取并返回响应中的数据
        if (response.status === 200) {
            return response.data; // 返回 '123456' 或其他响应数据
        } else {
            throw new Error(`Unexpected status code: ${response.status}`);
        }
    } catch (error) {
        // 捕获并处理请求错误
        console.error('Error fetching register:', error.message); // 打印错误信息到控制台
        throw error; // 重新抛出错误,以便调用者可以进一步处理
    }
}

6、CustomerPage.vue

<template>
  <!-- 用户列表表格 -->
  <el-table v-loading="loading" :data="customerList">
    <el-table-column type="index" label="序号" width="100px" align="center" />
    <el-table-column label="账号" align="center" prop="account"/>
    <el-table-column label="角色" align="center" prop="role"/>
<!--    <el-table-column label="头像" align="center" prop="customerHeadProtrait"/>-->
    <el-table-column label="昵称" align="center" prop="nickName"/>
    <el-table-column label="出生日期" align="center" prop="birthday"/>
    <el-table-column label="年龄" align="center" prop="age"/>
    <el-table-column label="性别" align="center" prop="sex"/>
    <el-table-column label="创建时间" align="center" prop="createTime"/>
    <el-table-column label="更新时间" align="center" prop="updateTime"/>
    <el-table-column label="是否正常使用" align="center" prop="isDelete"/>
  </el-table>
</template>

<script>
import { ref, onMounted } from 'vue';
import { selCustomersJs } from '@/module/rabbit-system/api/customer';

export default {
  name: 'CustomerPage',
  setup() {
    const loading = ref(true);
    const customerList = ref([]);
    // 页面加载时调用方法
    onMounted(() => {
      selCustomersVue();
    });

    // 批量处理数字英文转换
    const processCustomerData = (customers) => {
      customers.forEach(customer => {
        // 处理 role
        if (customer.role === "1") {
          customer.role = "超级管理员";
        } else if (customer.role === "2") {
          customer.role = "管理员";
        } else if (customer.role === "3") {
          customer.role = "普通用户";
        }
        // 处理 sex
        if (customer.sex === "0") {
          customer.sex = "女";
        } else if (customer.sex === "1") {
          customer.sex = "男";
        } else if (customer.sex === "2") {
          customer.sex = "保密";
        }
        // 处理 isDelete
        if (customer.isDelete === "N") {
          customer.isDelete = "是";
        } else if (customer.isDelete === "Y") {
          customer.isDelete = "否";
        }
      });
      return customers;
    };

    // 获取用户数据
    const selCustomersVue = async () => {
      loading.value = true;
      try {
        const response = await selCustomersJs();
        // 批量处理数据
        const customers = processCustomerData(response.data.data);
        customerList.value = customers;

      } catch (error) {
        console.error('Error fetching customers:', error);
      } finally {
        loading.value = false;
      }
    };

    return {
      loading,
      customerList,
    };
  },
};
</script>

<style scoped>
/* 添加样式 */
</style>

7、LoginPage.vue

<template>
  <div v-loading = "loading" style="background: linear-gradient(to left, #237ba2, #53d014);width: 100%; height: 100vh; position: fixed;">
    <div style="display: inline-block;top: 200px; left: 200px;position: fixed;">
      <span style="font-size: 60px;color: #FFA500;">欢迎登录兔子系统</span>
    </div>
    <div style="margin-left: 600px;">
      <div style="margin-top: 250px;">
        <div>
          <el-avatar :src="require('@/assets/兔子来了.jpeg')" style="width: 100px;height: 100px"/>
        </div>
        <div style="margin-top: 20px">
          账号:<el-input v-model="account" style="width: 240px" placeholder="请输入账号" />
        </div>
        <div style="margin-top: 10px">
          密码:<el-input v-model="password" type="password" style="width: 240px" placeholder="请输入密码" show-password/>
        </div>
        <div class="mb-4" style="margin-top: 10px">
          <el-button type="primary" @click="register">注册</el-button>
          <el-button type="success" @click="login">登录</el-button>
        </div>
        <div style="margin-top: 10px">
          <el-link type="danger" style="font-size: 10px" @click="forgetPassword">忘记密码?</el-link>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import { ref } from 'vue'
import router from "@/router";
import {loginJS} from "@/module/rabbit-system/api/registerAndLogin";
import {ElMessageBox} from "element-plus";

export default {
  name: 'LoginPage',
  setup() {
    const form ={
      account: "",
      password: "",
    };
    // 定义一个 delay 函数,用于处理异步延迟
    function delay(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }
    // 定义响应式变量
    const loading = ref(false);
    const account = ref("");
    const password = ref("");
    const register = () => {
      // router.replace('/RegisterPage'); 浏览器可以返回,不销毁当前页面
      router.replace('/RegisterPage'); // 浏览器不可以返回,销毁当前页面
    };

    // 一个提示弹窗的方法
    const popUpWindows = (msg) => {
      ElMessageBox.alert(msg, '温馨提示', {
        confirmButtonText: '确认',
      })
    };
    // 登录按钮
    const login = async() => {
      if (account.value.length === 0){
        popUpWindows("请输入您的账号");
      }else if (password.value.length === 0){
        popUpWindows("请输入您的密码");
      }else {
        form.account = account.value;
        form.password = password.value;
        const accountSessionStorage = account.value;
        // 开始登录
        loading.value = true;
        const data = await loginJS(form);
        await delay(1000);
        if (data.data.i === 0){
          loading.value = false;
          popUpWindows(data.msg);
        }else if (data.data.i === 1){
          loading.value = false;
          // 存储 sessionStorage ,他是 Web 存储机制,用于在用户会话期间存储数据,就是当前页面在就在,页面关闭就没了,可以存储一些用户信息等
          sessionStorage.setItem('account',accountSessionStorage); // 将用户的账号存储到 sessionStorage
          await router.replace('/CustomerPage');
          popUpWindows(data.msg);
        }else {
          loading.value = false;
          popUpWindows(data.msg);
        }
      }
    };

    const forgetPassword = () => {
      router.replace('/ForgetPasswordPage');
    };

    return {
      loading,
      account,
      password,
      login,
      register,
      popUpWindows,
      forgetPassword,
    };
  }
}
</script>

<style scoped>
/* 你的样式代码 */
</style>

8、RegisterPage.vue

<template>
  <div v-loading = "loading" style="background: linear-gradient(to right, #237ba2, #53d014);width: 100%; height: 100vh; position: fixed;">
    <div style="display: inline-block;top: 200px; left: 200px;position: fixed;">
      <span style="font-size: 60px;color: #FFA500;">欢迎注册兔子系统</span>
    </div>
    <div style="margin-left: 600px;">
      <div style="margin-top: 60px">
        <el-avatar :src="require('@/assets/兔子来了.jpeg')" style="width: 100px;height: 100px"/>
      </div>
      <div style="margin-top: 20px">
        请输入昵称:<el-input v-model="nickName" style="width: 240px" placeholder="请输入昵称" />
      </div>
      <div style="margin-top: 10px">
        请选择性别:<el-radio-group v-model="sex"> <!--v-model="sex" 用于双向绑定-->
                    <el-radio value="1" border style="width: 74px;">男</el-radio>
                    <el-radio value="0" border style="width: 74px;margin-left: -20px">女</el-radio>
                    <el-radio value="2" border style="width: 74px;margin-left: -20px">保密</el-radio>
                  </el-radio-group>
      </div>
      <div style="margin-top: 10px">
        请选择生日:<el-date-picker
                    type="date"
                    placeholder="出生日期"
                    style="width: 240px"
                    v-model="birthday"
                 />
      </div>
      <div style="margin-top: 10px">
        请输入密码:<el-input
                      v-model="password1"
                      style="width: 240px"
                      type="password"
                      placeholder="请输入密码"
                      show-password
                  />
      </div>
      <div style="margin-top: 10px">
        请输入密码:<el-input
                      v-model="password2"
                      style="width: 240px"
                      type="password"
                      placeholder="请输入密码"
                      show-password
                  />
      </div>
      <div style="margin-top: 5px;">
        <!-- v-if:当你需要根据条件动态地添加或移除元素时,使用 v-if。它会完全从 DOM 中移除元素,不会占用空间。-->
        <el-text v-if="password1 !== password2" type="danger">两次输入的密码不一致!</el-text>
      </div>
      <div style="margin-top: 10px;">
        <el-dropdown split-button trigger="click" @command="(command) => customerSafetyPlus(command)">
          <span style="width: 274px">{{ safetyProblem1 }}</span>
          <span style="width: 274px;display: none">{{ safetyProblem1Id }}</span>
          <template #dropdown>
            <el-dropdown-menu>
              <el-dropdown-item :command="'1'+'/'+item.problem+'/'+item.id" v-for="item in questions" :key = "item">
                {{ item.problem }}
              </el-dropdown-item>
            </el-dropdown-menu>
          </template>
        </el-dropdown>
      </div>
      <div>
        <el-input v-model="safetyProblem1Answer" v-if="safetyProblem1!='请选择安全问题1'" style="width: 338px;margin-top: 5px" placeholder="请输入安全问题的答案" />
      </div>
      <div style="margin-top: 10px;">
        <el-dropdown split-button trigger="click" @command="(command) => customerSafetyPlus(command)">
          <span style="width: 274px">{{ safetyProblem2 }}</span>
          <span style="width: 274px;display: none">{{ safetyProblem2Id }}</span>
          <template #dropdown>
            <el-dropdown-menu>
              <el-dropdown-item :command="'2'+'/'+item.problem+'/'+item.id" v-for="item in questions" :key = "item">
                {{ item.problem }}
              </el-dropdown-item>
            </el-dropdown-menu>
          </template>
        </el-dropdown>
      </div>
      <div>
        <el-input v-model="safetyProblem2Answer" v-if="safetyProblem2!='请选择安全问题2'" style="width: 338px;margin-top: 5px" placeholder="请输入安全问题的答案" />
      </div>
      <div style="margin-top: 10px;">
        <el-dropdown split-button trigger="click" @command="(command) => customerSafetyPlus(command)">
          <span style="width: 274px">{{ safetyProblem3 }}</span>
          <span style="width: 274px;display: none">{{ safetyProblem3Id }}</span>
          <template #dropdown>
            <el-dropdown-menu>
              <el-dropdown-item :command="'3'+'/'+item.problem+'/'+item.id" v-for="item in questions" :key = "item">
                {{ item.problem }}
              </el-dropdown-item>
            </el-dropdown-menu>
          </template>
        </el-dropdown>
      </div>
      <div>
        <el-input v-model="safetyProblem3Answer" v-if="safetyProblem3!='请选择安全问题3'" style="width: 338px;margin-top: 5px" placeholder="请输入安全问题的答案" />
      </div>
      <div class="mb-4" style="margin-top: 10px;">
        <!-- :style="{ width: '340px' }" 设置按钮宽度 -->
        <el-button type="primary" @click="goBack">返回</el-button>
        <el-button type="primary" @click="register">注册</el-button>
      </div>
    </div>
  </div>
</template>

<script>
import {computed, onMounted, ref, } from 'vue';
import router from "@/router";
import {ElMessageBox} from "element-plus";
import {registerJS, selSafetyProblemsJS} from "@/module/rabbit-system/api/registerAndLogin";
export default {
  name: 'LoginPage',
  setup() {
    // 页面一打开就执行
    onMounted(() => {
      loadQuestions();
    });
    const form ={
      nickName: "",
      sex: "",
      age: "",
      birthday: "",
      password: "",
      safetyProblem1Id: "",
      safetyProblem2Id: "",
      safetyProblem3Id: "",
      safetyProblem1Answer: "",
      safetyProblem2Answer: "",
      safetyProblem3Answer: "",
    };
    // 定义响应式变量
    const loading = ref(false);
    const nickName = ref("");
    // v-model="sex" 用于双向绑定 ,这里是给组件赋一个默认的值
    const sex = ref('')
    const birthday = ref("");
    const password1 = ref("");
    const password2 = ref("");
    let safetyProblem1 = ref('请选择安全问题1'); // 确保在这里声明 safetyProblem1
    let safetyProblem2 = ref('请选择安全问题2');
    let safetyProblem3 = ref('请选择安全问题3');
    let safetyProblem1Id = ref('');
    let safetyProblem2Id = ref('');
    let safetyProblem3Id = ref('');
    const safetyProblem1Answer = ref("");
    const safetyProblem2Answer = ref("");
    const safetyProblem3Answer = ref("");

    // let 可以在下面的函数内部重新赋值
    let message;
    // 一个提示弹窗的方法
    const popUpWindows = (msg) => {
      ElMessageBox.alert(msg, '温馨提示', {
        confirmButtonText: '确认',
      })
    };
    // 定义了一个变量,并附加获取年龄的方法,直接将选择的日期转换伟年龄返回
    const age = computed(() => {
      if (!birthday.value) return null; // 如果生日未选择则返回 null
      const today = new Date();
      const birthDate = new Date(birthday.value);
      let age = today.getFullYear() - birthDate.getFullYear();
      const monthDiff = today.getMonth() - birthDate.getMonth();
      // 如果生日还未到,年龄减 1
      if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
        age--;
      }
      return age;
    });
    // 定义一个 delay 函数,用于处理异步延迟
    function delay(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }
    // 重置表单数据
    const resetForm = () => {
      nickName.value = "";
      sex.value = "";
      birthday.value = "";
      password1.value = "";
      password2.value = "";
      safetyProblem1.value = '请选择安全问题1';
      safetyProblem2.value = '请选择安全问题2';
      safetyProblem3.value = '请选择安全问题3';
      safetyProblem1Id.value = '';
      safetyProblem2Id.value = '';
      safetyProblem3Id.value = '';
      safetyProblem1Answer.value = "";
      safetyProblem2Answer.value = "";
      safetyProblem3Answer.value = "";
    };

    // 加载安全问题
    let questions = ref([]);
    const loadQuestions = async () => {
      try {
        const response = await selSafetyProblemsJS();
        const dd  = response.data.data;
        questions.value = dd;
      } catch (error) {
        console.error('获取问题失败:', error);
      }
    };
    // 选择安全问题后将安全问题及id渲染到标签里
    const customerSafetyPlus = (command) => {
      const [order,problem, id] = command.split('/');
      if (order === '1'){
        safetyProblem1Id.value = id;
        safetyProblem1.value = problem;
      }else if (order === '2'){
        safetyProblem2Id.value = id;
        safetyProblem2.value = problem;
      }else if (order === '3'){
        safetyProblem3Id.value = id;
        safetyProblem3.value = problem;
      }
    }

    // 返回按钮
    const goBack = () => {
      router.replace('/LoginPage');
    };
    // 注册按钮
    const register = async() => {
      if (nickName.value == "" || sex.value == "" || birthday.value == "" || password1.value == "" || password2.value == "") {
        message = "请完善用户信息!";
        popUpWindows(message);
      } else if (nickName.value.length < 2) {
        message = "昵称长度不得小于2位!";
        popUpWindows(message);
      } else if (password1.value.length < 6 || password2.value.length < 6 || password2.value.length > 20 || password2.value.length > 20) {
        message = "密码长度不得小于6位,不得大于20位!";
        popUpWindows(message);
      } else if (password1.value !== password2.value) {
        message = "两次输入的密码不一致!";
        popUpWindows(message);
      } else if (age.value < 18 && age.value != null) {
        message = "年龄必须大于18岁!";
        popUpWindows(message);
      } else if (safetyProblem1.value == '请选择安全问题1' || safetyProblem2.value == '请选择安全问题2' || safetyProblem3.value == '请选择安全问题3') {
        message = "请选择3个安全问题!";
        popUpWindows(message);
      } else if (safetyProblem1Answer.value.length == 0) {
        message = "请回答第一个问题!";
        popUpWindows(message);
      } else if (safetyProblem2Answer.value.length == 0) {
        message = "请回答第二个问题!";
        popUpWindows(message);
      } else if (safetyProblem3Answer.value.length == 0) {
        message = "请回答第三个问题!";
        popUpWindows(message);
      } else if (safetyProblem1Answer.value.length > 20 || safetyProblem2Answer.value.length > 20 || safetyProblem3Answer.value.length > 20) {
        message = "答案字数不得超过20个字!";
        popUpWindows(message);
      }else {
        form.nickName= nickName.value;
        form.sex= sex.value;
        form.age= age.value;
        form.birthday = birthday.value;
        form.password= password1.value;
        form.safetyProblem1Id= safetyProblem1Id.value;
        form.safetyProblem2Id= safetyProblem2Id.value;
        form.safetyProblem3Id= safetyProblem3Id.value;
        form.safetyProblem1Answer= safetyProblem1Answer.value;
        form.safetyProblem2Answer= safetyProblem2Answer.value;
        form.safetyProblem3Answer= safetyProblem3Answer.value;
        // 开始注册
        loading.value = true;
        // 设置两秒延迟(异步延迟,不影响代码运行)
        // setTimeout(() => {
        //   response = registerJS();
        // }, 5000);
        const data = await registerJS(form);
        await delay(1000);
        loading.value = false;
        if (data.data.i > 0){
          popUpWindows(data.msg);
          await router.replace('/LoginPage');
        }else {
          popUpWindows(data.msg);
          resetForm();  // 清空表单数据
        }
      }
    };

      return {
        loading,
        questions,
        nickName,
        sex,
        birthday,
        password1,
        password2,
        safetyProblem1,
        safetyProblem2,
        safetyProblem3,
        safetyProblem1Id,
        safetyProblem2Id,
        safetyProblem3Id,
        safetyProblem1Answer,
        safetyProblem2Answer,
        safetyProblem3Answer,
        goBack,
        register,
        popUpWindows,
        customerSafetyPlus
      };
    }
}
</script>

<style scoped>
/* 你的样式代码 */
</style>

9、RetrievePassword.vue

<template>
  <div v-loading = "loading" style="background: linear-gradient(to top, #237ba2, #53d014);width: 100%; height: 100vh; position: fixed;">
    <div style="display: inline-block;top: 200px; left: 200px;position: fixed;">
      <span style="font-size: 60px;color: #FFA500;">忘记密码了吧,哈哈!</span>
    </div>
    <div style="margin-left: 600px;">
      <div style="margin-top: 200px">
        请输入账号:<el-input v-model="account" style="width: 240px" placeholder="请输入您的账号" />
      </div>
      <div style="margin-top: 10px;">
        <el-dropdown split-button trigger="click" @command="(command) => customerSafetyPlus(command)">
          <span style="width: 274px">{{ safetyProblem1 }}</span>
          <span style="width: 274px;display: none">{{ safetyProblem1Id }}</span>
          <template #dropdown>
            <el-dropdown-menu>
              <el-dropdown-item :command="'1'+'/'+item.problem+'/'+item.id" v-for="item in questions" :key = "item">
                {{ item.problem }}
              </el-dropdown-item>
            </el-dropdown-menu>
          </template>
        </el-dropdown>
      </div>
      <div>
        <el-input v-model="safetyProblem1Answer" v-if="safetyProblem1!='请选择安全问题1'" style="width: 338px;margin-top: 5px" placeholder="请输入安全问题的答案" />
      </div>
      <div style="margin-top: 10px;">
        <el-dropdown split-button trigger="click" @command="(command) => customerSafetyPlus(command)">
          <span style="width: 274px">{{ safetyProblem2 }}</span>
          <span style="width: 274px;display: none">{{ safetyProblem2Id }}</span>
          <template #dropdown>
            <el-dropdown-menu>
              <el-dropdown-item :command="'2'+'/'+item.problem+'/'+item.id" v-for="item in questions" :key = "item">
                {{ item.problem }}
              </el-dropdown-item>
            </el-dropdown-menu>
          </template>
        </el-dropdown>
      </div>
      <div>
        <el-input v-model="safetyProblem2Answer" v-if="safetyProblem2!='请选择安全问题2'" style="width: 338px;margin-top: 5px" placeholder="请输入安全问题的答案" />
      </div>
      <div style="margin-top: 10px;">
        <el-dropdown split-button trigger="click" @command="(command) => customerSafetyPlus(command)">
          <span style="width: 274px">{{ safetyProblem3 }}</span>
          <span style="width: 274px;display: none">{{ safetyProblem3Id }}</span>
          <template #dropdown>
            <el-dropdown-menu>
              <el-dropdown-item :command="'3'+'/'+item.problem+'/'+item.id" v-for="item in questions" :key = "item">
                {{ item.problem }}
              </el-dropdown-item>
            </el-dropdown-menu>
          </template>
        </el-dropdown>
      </div>
      <div>
        <el-input v-model="safetyProblem3Answer" v-if="safetyProblem3!='请选择安全问题3'" style="width: 338px;margin-top: 5px" placeholder="请输入安全问题的答案" />
      </div>
      <div class="mb-4" style="margin-top: 10px;">
        <!-- :style="{ width: '340px' }" 设置按钮宽度 -->
        <el-button type="primary" @click="goBack">返回</el-button>
        <el-button type="primary" @click="retrievePassword">找回密码</el-button>
      </div>
    </div>
  </div>
  <el-dialog v-model="updPasswordForm" title="请输入您的新密码" width="250" style="margin-top: 30vh;" :close-on-click-modal="false"> <!-- close-on-click-modal 控制点击外部是否关闭-->
    <el-input v-model="newPassword1" type="password" autocomplete="off" placeholder="请输入您的新密码"/><!-- autocomplete="off" 防止自动填充 -->
    <el-input v-model="newPassword2" type="password" autocomplete="off" placeholder="请再次输入您的新密码" style="margin-top: 5px"/><!-- autocomplete="off" 防止自动填充 -->
    <div style="margin-top: 5px;">
      <!-- v-if:当你需要根据条件动态地添加或移除元素时,使用 v-if。它会完全从 DOM 中移除元素,不会占用空间。-->
      <el-text v-if="newPassword1 !== newPassword2" type="danger">两次输入的密码不一致!</el-text>
    </div>
    <el-button style="margin-top: 10px" @click="updPasswordForm = false">取消</el-button>
    <el-button style="margin-top: 10px" @click="updPassword">确定</el-button>
  </el-dialog>
</template>
<script>
import {onMounted, ref, } from 'vue';
import router from "@/router";
import { ElMessageBox } from 'element-plus';
import {selSafetyProblemsJS} from "@/module/rabbit-system/api/registerAndLogin";
import {retrievePasswordJs,updPasswordJS} from "@/module/rabbit-system/api/retrievePassword";

export default {
  name: 'LoginPage',
  setup() {
    // 页面一打开就执行
    onMounted(() => {
      loadQuestions();
    });
    const form ={
      account: "",
      safetyProblem1Id: "",
      safetyProblem2Id: "",
      safetyProblem3Id: "",
      safetyProblem1Answer: "",
      safetyProblem2Answer: "",
      safetyProblem3Answer: "",
      password:"",
    };
    // 定义响应式变量
    const loading = ref(false);
    const account = ref("");
    let safetyProblem1 = ref('请选择安全问题1');
    let safetyProblem2 = ref('请选择安全问题2');
    let safetyProblem3 = ref('请选择安全问题3');
    let safetyProblem1Id = ref('');
    let safetyProblem2Id = ref('');
    let safetyProblem3Id = ref('');
    const safetyProblem1Answer = ref("");
    const safetyProblem2Answer = ref("");
    const safetyProblem3Answer = ref("");
    const newPassword1 = ref("");
    const newPassword2 = ref("");
    const updPasswordForm = ref(false);

    // let 可以在下面的函数内部重新赋值
    let message;
    // 一个提示弹窗的方法2.0,并且点击确定之后再执行后面的代码
    const popUpWindows = (msg) => {
      return new Promise((resolve, reject) => {
        ElMessageBox.alert(msg, '温馨提示', {
          confirmButtonText: '确认',
        })
            .then(() => {
              resolve();  // 用户点击了确认按钮
            })
            .catch(() => {
              reject();  // 用户点击了取消或关闭弹窗
            });
      });
    };

    // 定义一个 delay 函数,用于处理异步延迟
    function delay(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }

    // 加载安全问题
    let questions = ref([]);
    const loadQuestions = async () => {
      try {
        const response = await selSafetyProblemsJS();
        const dd  = response.data.data;
        questions.value = dd;
      } catch (error) {
        console.error('获取问题失败:', error);
      }
    };
    // 选择安全问题后将安全问题及id渲染到标签里
    const customerSafetyPlus = (command) => {
      const [order,problem, id] = command.split('/');
      if (order === '1'){
        safetyProblem1Id.value = id;
        safetyProblem1.value = problem;
      }else if (order === '2'){
        safetyProblem2Id.value = id;
        safetyProblem2.value = problem;
      }else if (order === '3'){
        safetyProblem3Id.value = id;
        safetyProblem3.value = problem;
      }
    }

    // 返回按钮
    const goBack = () => {
      router.replace('/LoginPage');
    };

    // 找回密码按钮
    const retrievePassword = async() => {
      if (account.value == "") {
        message = "请完善用户信息!";
        await popUpWindows(message);
      } else if (safetyProblem1.value == '请选择安全问题1' || safetyProblem2.value == '请选择安全问题2' || safetyProblem3.value == '请选择安全问题3') {
        message = "请选择3个安全问题!";
        await popUpWindows(message);
      } else if (safetyProblem1Answer.value.length == 0) {
        message = "请回答第一个问题!";
        await popUpWindows(message);
      } else if (safetyProblem2Answer.value.length == 0) {
        message = "请回答第二个问题!";
        await popUpWindows(message);
      } else if (safetyProblem3Answer.value.length == 0) {
        message = "请回答第三个问题!";
        await popUpWindows(message);
      } else {
        form.account= account.value;
        form.safetyProblem1Id= safetyProblem1Id.value;
        form.safetyProblem2Id= safetyProblem2Id.value;
        form.safetyProblem3Id= safetyProblem3Id.value;
        form.safetyProblem1Answer= safetyProblem1Answer.value;
        form.safetyProblem2Answer= safetyProblem2Answer.value;
        form.safetyProblem3Answer= safetyProblem3Answer.value;
        loading.value = true;
        const data = await retrievePasswordJs(form);
        await delay(1000);
        loading.value = false;
        if (data.code == 200){
          updPasswordForm.value = true;
        }else {
          await popUpWindows(data.msg);
        }
      }
    };

    // 修改密码
    const updPassword = async () => {
      if (newPassword1.value != newPassword2.value){
        await popUpWindows("两次输入的密码不一致!");
      }else if (newPassword1.value.length < 6 || newPassword1.value.length > 20){
        await popUpWindows("密码的长度应该在6到20之间!");
      }else {
        form.account = account.value;
        form.password = newPassword1.value;
        const data = await updPasswordJS(form);
        newPassword1.value = "";
        newPassword2.value = "";
        updPasswordForm.value = false;
        loading.value = true;
        await delay(1000);
        loading.value = false;
        await popUpWindows(data.msg);
        if (data.code == 200){
          await router.replace('/LoginPage');
        }
      }
    };

    return {
      loading,
      questions,
      account,
      safetyProblem1,
      safetyProblem2,
      safetyProblem3,
      safetyProblem1Id,
      safetyProblem2Id,
      safetyProblem3Id,
      safetyProblem1Answer,
      safetyProblem2Answer,
      safetyProblem3Answer,
      updPasswordForm,
      newPassword1,
      newPassword2,
      goBack,
      retrievePassword,
      popUpWindows,
      customerSafetyPlus,
      updPassword
    };
  }
}
</script>

<style scoped>
/* 你的样式代码 */
</style>

10、index.js

// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import App from '@/App';
import CustomerPage from '@/module/rabbit-system/views/CustomerPage';
import LoginPage from '@/module/rabbit-system/views/LoginPage';
import RegisterPage from '@/module/rabbit-system/views/RegisterPage';
import ForgetPasswordPage from '@/module/rabbit-system/views/RetrievePassword';
import NotFound from '@/NotFoundPage';

// 检查用户是否已登录
function isAuthenticated() {
    return !!sessionStorage.getItem('account'); // 假设存储用户信息的键为 'user'
}

const routes = [
    { path: '/', component: App },
    { path: '/CustomerPage', component: CustomerPage, meta: { requiresAuth: true } },
    { path: '/LoginPage', component: LoginPage },
    { path: '/RegisterPage', component: RegisterPage },
    { path: '/ForgetPasswordPage', component: ForgetPasswordPage },
    { path: '/:pathMatch(.*)*', component: NotFound }
];

const router = createRouter({
    history: createWebHistory(),
    routes,
});

// 全局前置守卫,如果没有登录直接访问内容页面将重新定位
router.beforeEach((to, from, next) => {
    if (to.matched.some(record => record.meta.requiresAuth)) {
        // 如果目标路由需要认证且用户未认证
        if (!isAuthenticated()) {
            next('/LoginPage'); // 重定向到登录页面
        } else {
            next(); // 允许访问
        }
    } else {
        next(); // 允许访问
    }
});

export default router;

11、App.vue

<template>
  <img alt="Vue logo" src="./assets/logo.png">
  <HelloWorld msg="Welcome to Your Vue.js App"/>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'App',
  components: {
    HelloWorld
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

12、main.js

import { createApp } from 'vue';
import RabbitSystemVueApp from './RabbitSystemVueApp'; // 引入主 Vue 组件
import router from './router'; // 引入路由配置
import ElementPlus from 'element-plus'; // 引入 Element Plus UI 库
import 'element-plus/dist/index.css'; // 引入 Element Plus 的样式文件
import zhCn from 'element-plus/es/locale/lang/zh-cn'; // 引入中文语言包

// 创建 Vue 应用实例
const app = createApp(RabbitSystemVueApp);

// 使用插件
app.use(router); // 注册路由插件
app.use(ElementPlus, { locale: zhCn }); // 注册 Element Plus 插件并引用中文包
// 挂载应用到 DOM 元素
app.mount('#app'); // 将 Vue 应用挂载到 id 为 'app' 的 DOM 元素上

13、NotFoundPage.vue

<!-- src/components/NotFound.vue -->
<template>
  <div class="not-found">
    <div class="container">
      <h1>404</h1>
      <p>哎呀!我们找不到你要的页面。</p>
      <router-link class="home-link" to="/LoginPage">返回首页</router-link>
    </div>
  </div>
</template>

<script setup>
</script>

<style scoped>
.not-found {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100vh;
  background: linear-gradient(135deg, #61E7FFFF, #d7a1b2);
  color: #fff;
  font-family: 'Arial', sans-serif;
}

.container {
  text-align: center;
  background: rgba(0, 0, 0, 0.6);
  border-radius: 10px;
  padding: 40px;
  max-width: 500px;
  width: 100%;
}

h1 {
  font-size: 120px;
  margin: 0;
  font-weight: bold;
  text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
}

p {
  font-size: 18px;
  margin: 20px 0;
  line-height: 1.5;
}

.home-link {
  display: inline-block;
  padding: 10px 20px;
  margin-top: 20px;
  font-size: 16px;
  color: #fff;
  background-color: #42b983;
  border-radius: 5px;
  text-decoration: none;
  transition: background-color 0.3s, transform 0.3s;
}

.home-link:hover {
  background-color: #359d77;
  transform: scale(1.05);
}

.home-link:focus {
  outline: none;
}
</style>

14、RabbitSystemVueApp.vue

<template>
  <router-view>

  </router-view> <!-- 渲染匹配的路由组件 -->
</template>

<script>

</script>

<style scoped>

</style>

15、vue.config.js

const { defineConfig } = require('@vue/cli-service');
const { DefinePlugin } = require('webpack');
const path = require('path');

module.exports = defineConfig({
  // 配置开发服务器
  devServer: {
    port: 8082, // 设置开发服务器的端口号为 8082
  },
  // 是否转译依赖(依赖于 babel)
  transpileDependencies: true,

  // 配置公共路径,根据环境变量设置不同的路径
  publicPath: process.env.NODE_ENV === 'production' ? '/myapp/' : '/',

  // 配置 webpack
  configureWebpack: {
    plugins: [
      new DefinePlugin({
        // 定义全局常量,用于在生产环境中关闭 Vue 的生产模式水合不匹配的详细信息
        __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: JSON.stringify(false)
      }),
    ],
  },
});

        这些大致就是全部的代码了,基本的一些注释我都加上去了,如果还有问题的话可以在评论区讨论,另外篇幅比较多,如果有漏掉的部分也可以提醒我补上去。

(周末的时候有空了才会写一写,所以比较慢)

结束...

实现头像上传,可以结合Spring Boot后端框架,Vue前端框架以及Element UI组件库进行实现。 首先,在Vue前端页面中,可以使用Element UI中的Upload组件实现文件上传功能。可以在页面中定义一个Upload组件,设置action属性为上传接口的URL,设置headers属性为请求头部信息,设置on-success属性为上传成功后的回调函数。具体代码如下: ``` <template> <div> <el-upload class="avatar-uploader" action="/api/uploadAvatar" :headers="{ Authorization: 'Bearer ' + token }" :show-file-list="false" :on-success="handleSuccess"> <img v-if="imageUrl" :src="imageUrl" class="avatar"> <i v-else class="el-icon-plus avatar-uploader-icon"></i> </el-upload> </div> </template> <script> import { getToken } from '@/utils/auth' export default { data() { return { imageUrl: '', token: getToken() } }, methods: { handleSuccess(response) { this.imageUrl = response.data.url } } } </script> ``` 其中,token是用于认证的令牌,可以通过getToken函数获取。handleSuccess函数是上传成功后的回调函数,其中response.data.url表示上传成功后的图片URL。 然后,在Spring Boot后端接口中,可以使用Spring MVC的注解@RequestParam来接收上传的文件。具体代码如下: ``` @RestController @RequestMapping("/api") public class UploadController { @PostMapping("/uploadAvatar") public JsonResult uploadAvatar(@RequestParam("file") MultipartFile file) throws IOException { // 处理上传的文件 return JsonResult.ok("url", "http://www.example.com/avatar.jpg"); } } ``` 其中,@PostMapping注解表示接收POST请求,@RequestParam("file")注解表示接收名为file的文件参数。处理上传的文件后,可以返回一个JsonResult对象,其中包含上传成功后的图片URL。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值