抽奖系统-基本-注册

前言

数据库建立

在这里插入图片描述

在这里插入图片描述

分别是用户表,活动表,奖品表,活动人员关联表,中奖记录表,活动奖品关联表

⼈员业务模块:管理员注册、登录,及普通⽤⼾的创建。
• 活动业务模块:活动管理及活动状态管理。
• 奖品业务模块:奖品管理与奖品的分配。还包括奖品图的上传。
• 通知业务模块:发送短信、邮件等业务,例如验证码发送,中奖通知。
• 抽奖业务模块:完成抽奖动作,以及抽奖后的结果展⽰

唯一索引用uk开头,主键索引用pk开头,普通索引就用idx开头

我们直接用mysql的source命令,执行sql脚本,然后创造数据库

数据库source命令也是.命令

sql脚本就是写它的路径,路径不要有中文
在这里插入图片描述
在这里插入图片描述

创建工程及分层

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

错误码定义

在这里插入图片描述

我们不对dao层进行错误码定义,dao的错误码被service包含就可以了
所以我们只定义controller和service层的错误码,还有一个全局的错误码
在这里插入图片描述
我们的错误码就是一个类,错误码类

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

定义异常类

在这里插入图片描述
在这里插入图片描述

定义同一结果返回类

在这里插入图片描述

Jackson序列化工具

在这里插入图片描述
序列化和反序列化就是利用objectMapper的readValue和writevalueasString方法来的
然后就是list的反序列化方法有点不一样

在这里插入图片描述

在这里插入图片描述
但是这个方法要抛异常,很麻烦,我们可以写在工具包里面

在这里插入图片描述
在这里插入图片描述

然后是测试
在这里插入图片描述

日志配置

在这里插入图片描述
第一个是指定日志的配置文件在哪里
第二个是指定当前环境
在这里插入图片描述

这个就是配置文件了

在这里插入图片描述
这种是日志的输出格式

在这里插入图片描述
这个就是info的过滤器,我们要自定义过滤器,不然所有的info日志都打印进文件了
我们自定义过滤器的话,那么打印进文件的日志就是我们需要的日志了

在这里插入图片描述

这段代码定义了一个名为 InfoLevelFilter 的 Logback 过滤器,其作用是只允许 INFO 级别的日志事件通过,其他级别的日志(如 DEBUG、WARN、ERROR 等)都会被过滤掉

我们需要的就是info日志
这个过滤器得到作用就是只要Logback 的info日志

现在我们来测试一下控制台输出的日志格式变没有
我们本地是用控制台输出日志,服务器是把日志输出到文件中

在这里插入图片描述

在这里插入图片描述

我们用的打印日志的工具是SLF4J,当然也可以用注解SLF4J,我们配置了输出格式的话,SLF4J就不会用它的格式了

加密介绍

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
我们要对手机号和密码加密

密码用hash加密或者加盐加密
手机号就用对称加密
我们这里用hash加密密码
对称加密手机号

hash加密是不可逆的,只能加密,不能解密,只能根据加密结果来判断对不对
对称加密,只要知道了秘钥就是可逆的,知道了秘钥就可以加密解密了
加盐加密,就是先生成一个随机盐,然后随机盐和密码一起hash

实现加密工具

maven仓库

hutool工具
这里我们使用hutool工具来加密解密

在这里插入图片描述
使用哪个工具就引入哪个包
这里我们引入all,因为还要使用其他的
在这里插入图片描述

然后开始测试

        String s = DigestUtil.sha256Hex("123456789");

直接调用这个就可以hash加密了

在这里插入图片描述

    //手机号对称加密 aes
    @Test
    void aesTest(){
        //先搞个秘钥
        byte[] KEY = "123456789".getBytes(StandardCharsets.UTF_8);
        AES aes = SecureUtil.aes(KEY);//根据秘钥生成AES对象,这个对象就可以进行加密解密操作
        String s = aes.encryptHex("123456789");
        System.out.println("aes加密:"+s);
        String s1 = aes.decryptStr(s);
        System.out.println("aes解密:"+s1);
    }

在这里插入图片描述
在这里插入图片描述
但是直接报错了,原因就是这个秘钥的长度必须为16个字节

在这里插入图片描述
这样就可以了
秘钥必须为16个字节,或者24,或者32

注册功能分析

在这里插入图片描述

在这里插入图片描述

请求
{
 "name":"张三",
 "mail":"451@qq.com",
 "phoneNumber":"13188888888",
 "password":"123456789",
 "identity":"ADMIN"
}

[响应] 
{
 "code": 200,
 "data": {
 "userId": 22
 },
 "msg": ""
}

注册实现1-控制层

接口Serializable是为了序列化
注解RequestBody是为了接收请求参数为json的

        <!-- spring-boot 2.3及以上的版本只需要引⼊下⾯的依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

依赖可以对前端传入参数进行校验
如果是对json进行校验,要在json对象的前面先加上注解Validated
然后对参数加上对应的校验注解
注解NotBlank是对string类型进行的校验,校验为空,就会抛异常

还有就是service返回的对象也要单独定义,后缀为DTO
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

校验基本参数

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
校验身份信息要创建一个枚举类,然后看这个信息在枚举类里面存不存在
在这里插入图片描述
这个就是枚举类
ADMIN就是name,管理员就是message,然后枚举类需要构造函数和get方法
forname就是根据name获得枚举

在这里插入图片描述

邮箱是否存在

先引入依赖

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

校验手机号–typeHandler介绍

在这里插入图片描述

校验手机号

typeHandler就是这样得到,我们将String类型转化为Encrypt类型,这个类型具有这种效果
将Encrypt类型插入数据库时,会自动把String给加密,取出这个Encrypt数据时,会自动解密
当然加密和解密的操作都是我们自己要设置的

在这里插入图片描述

我们先创建一个对象封装String
因为typeHandler只能处理对象,所以要封装

在这里插入图片描述
这个就是typeHandler方法了,针对的是Encrypt类

然后还要添加两个注解


@MappedTypes(Encrypt.class)//被处理的类型
@MappedJdbcTypes(JdbcType.VARCHAR)//处理到数据库的类型,就是转化后的JDBC类型
public class EncryptTypeHandler extends BaseTypeHandler<Encrypt> {
    private static final byte[] KEY = "123456789abcdefg".getBytes(StandardCharsets.UTF_8);
    /**
     * 设置参数
     * @param ps SQL预编译对象
     * @param i 需要赋值的索引位置
     * @param parameter 原本位置i需要的值,就是我们要插入的值Encrypt
     * @param jdbcType 数据库类型
     * @throws SQLException 
     */
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Encrypt parameter, JdbcType jdbcType) throws SQLException {
        if(parameter == null || parameter.getValue() == null){
            ps.setString(i, null);//因为jdbcType类型为varchar类型,所以是setString
            return;
        }
        //加密
        AES aes = SecureUtil.aes(KEY);//创建AES对象
        String str = aes.encryptHex(parameter.getValue());//加密后的数据
        ps.setString(i, str);//存入到数据库中
        System.out.println("加密后的数据:" + str);
    }

    /**
     * 获取值
     * @param rs 数据库结果集
     * @param columnName 索引
     * @return
     * @throws SQLException
     */
    @Override
    public Encrypt getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return decrypt(rs.getString(columnName));
    }

    /**
     *
     * @param rs
     * @param columnIndex
     * @return
     * @throws SQLException
     */
    @Override
    public Encrypt getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return decrypt(rs.getString(columnIndex));
    }

    /**
     *
     * @param cs
     * @param columnIndex
     * @return
     * @throws SQLException
     */
    @Override
    public Encrypt getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return decrypt(cs.getString(columnIndex));
    }
    
    private Encrypt decrypt(String value){
        if(!StringUtils.hasText(value)){
            return null;
        }
        System.out.println("解密前的数据:" + value);
        return new Encrypt(SecureUtil.aes(KEY).decryptStr(value));
    }
}

这样我们就写好了

mybatis.type-handlers-package=com.ck.lotterysystem2.dao.handler

注意还要配置typeHandler路径才可以使用

在这里插入图片描述

在这里插入图片描述

这里的@Param(“phoneNumber”)是和#{phoneNumber}对应的
这个的意思就是把Encrypt phoneNumber转化成varchar类型的phoneNumber了
这个过程是会自动加密的

在这里插入图片描述
说明是可以用的

保存信息

先介绍一个插件Database Navigator
有了这个插件,那么我们的就可以在idea上看到数据库了
在这里插入图片描述

就是它,然后建立连接
在这里插入图片描述

也在这里

在这里插入图片描述

在这里插入图片描述
这样就有了

每个表都有id,创造时间,修改时间,那么我们就建一个类来存储这三个属性

在这里插入图片描述

而且后缀为DO就是专门为mapper准备的,与数据库紧贴的类

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
useGeneratedKeys = true
这个参数告诉 MyBatis 在执行插入操作后,需要获取数据库自动生成的主键值(如自增 ID)。对于支持自动生成主键的数据库(如 MySQL 的 AUTO_INCREMENT),启用此选项后,MyBatis 会自动将生成的主键值映射到实体类中
keyProperty = “id”
指定实体类中的哪个属性用于存储生成的主键值。在这个例子中,UserDO 类必须有一个名为 id 的属性(及其对应的 getter/setter 方法),生成的主键值会被设置到这个属性中。
keyColumn = “id”
指定数据库表中对应的主键列名。当实体类属性名与数据库表的列名不一致时,需要通过此参数显式映射。在此例中,数据库表的主键列名是 id,与实体类属性名一致。
在这里插入图片描述

在这里插入图片描述
这里就有数据了,如果没有数据的话,那么就取消链接,然后在连接,然后刷新就可以了

全局异常处理

我们用上注解@RestControllerAdvice+@ExceptionHandler

RestControllerAdvice是接收整个MVC的全部异常
而ExceptionHandler就是处理具体的异常了

我们在controller下建一个handler的包来处理这个事情


@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(value = ServiceException.class)
    public CommonResult<?> handleServiceException(ServiceException e) {
        log.error("handleServiceException:",e);
        return CommonResult.error(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode(),
                e.getMessage());
    }

    @ExceptionHandler(value = ControllerException.class)
    public CommonResult<?> handleControllerException(ControllerException e) {
        log.error("handleControllerException:",e);
        return CommonResult.error(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode(),
                e.getMessage());
    }

    @ExceptionHandler(value = Exception.class)
    public CommonResult<?> handleException(Exception e) {
        log.error("handleException:",e);
        return CommonResult.error(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode(),
                e.getMessage());
    }

}

这样就可以了

还有就是错误日志不需要占位符{}

在这里插入图片描述
注意这里断言要改一下,不是SUCCESS,或者改为非SUCCESS

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

这样就OK了

注册前端实现

在这里插入图片描述

登录时序图

在这里插入图片描述

阿里云短信服务

登录阿里云官网,实名认证
阿里云
在这里插入图片描述
点击ACCessKey
在这里插入图片描述

创建一个ACCessKey,然后保存

然后就可以开通短信服务了
直接开通短信服务

搜索短信服务
在这里插入图片描述
是这个界面

先申请资质

扫码钉钉
新增资质

在这里插入图片描述

然后是申请签名
在这里插入图片描述
在这里插入图片描述

签名就是名称
资质信息就是刚刚申请的资质
签名来源填写:测试或者学习
在这里插入图片描述

但是出现了这个问题,阿里云不能让学生使用短信了,所以阿里云不行了

然后是申请模版

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

结束,用不了了,要企业认证

Redis介绍

就是生成的随机验证码要放在Redis中
然后输入验证吧,就不要发送到手机号了,实现不了了暂时
在这里插入图片描述

redis配置使用

在这里插入图片描述


## redis  spring boot 3.x ##
spring.data.redis.host=localhost
spring.data.redis.port=6379
# 连接空闲超过N(s秒、ms毫秒)后关闭,0为禁⽤,这⾥配置值和tcp-keepalive值⼀致
spring.data.redis.timeout=60s
# 默认使⽤ lettuce 连接池
# 允许最⼤连接数,默认8(负值表⽰没有限制)
spring.data.redis.lettuce.pool.max-active=8
# 最⼤空闲连接数,默认8
spring.data.redis.lettuce.pool.max-idle=8
# 最⼩空闲连接数,默认0
spring.data.redis.lettuce.pool.min-idle=0
# 连接⽤完时,新的请求等待时间(s秒、ms毫秒),超过该时间抛出异常JedisConnectionException(默认-1,负值表⽰没有限制)
spring.data.redis.lettuce.pool.max-wait=5s

在这里插入图片描述
然后就可以链接上了,本地Windows端口号是配置好的
然后记得启动了项目才可以去链接,在idea上链接的时候

RedisUtil

这里我们使用的是StringRedisTemplate,这个是对RedisTemplate的分装,RedisTemplate最终存储在redis中的是字节数组
在这里插入图片描述

而StringRedisTemplate是的key和value都是String
而且最终存储在redis中的就是String

StringRedisTemplate这个是自动注入bean的,我们要在RedisUtil中使用StringRedisTemplate,那么
RedisUtil就要注入bean,用注解Configuration


@Configuration
@Slf4j
public class RedisUtil {
    @Autowired
    private static StringRedisTemplate stringRedisTemplate;

    public  boolean set(String key, String value) {
        try{
            stringRedisTemplate.opsForValue().set(key, value);
            return  true;
        } catch (Exception e) {
            log.error("redis set error:key:{},value:{},e:",key,value,e);//e不需要占位符
            throw new RuntimeException("redis set error");
        }
    }
    public static boolean set(String key, String value, Long expireTime) {
        try{
            stringRedisTemplate.opsForValue().set(key, value, expireTime, TimeUnit.SECONDS);//到了时间自动删除
            return  true;
        } catch (Exception e) {
            log.error("redis set error:key:{},value:{},expireTime,{},e:",key,value,expireTime,e);//e不需要占位符
            return false;
        }
    }
    public String get(String key) {
        try{
            return StringUtils.hasText(key)?stringRedisTemplate.opsForValue().get(key):null;
        } catch (Exception e) {
            log.error("redis get error:key:{},e:",key,e);//e不需要占位符
            return null;
        }
    }

    public boolean del(String... key) {
        try{
            if(key==null||key.length<=0)
                return false;
            if(key.length==1)
                stringRedisTemplate.delete(key[0]);
            stringRedisTemplate.delete(
                    (Collection<String>) CollectionUtils.arrayToList(key)
            );
            return true;
        } catch (Exception e) {
            log.error("redis del error:key:{},e:",key,e);//e不需要占位符
            return false;
        }
    }
    public boolean hasKey(String key){
        try{
            return StringUtils.hasText(key)? stringRedisTemplate.hasKey(key):false;

        } catch (Exception e) {
            log.error("redis hasKey error:key:{},e:",key,e);//e不需要占位符
            return false;
        }
    }


}

在这里插入图片描述测试一下

在这里插入图片描述

短信验证码服务完成

我们总共要实现两个service,一个是发送验证码,一个是获取验证码

在这里插入图片描述
在这里插入图片描述

验证码我们还是使用hutool的工具

public class CaptchaUtil {
    public static String getCaptcha(int length) {
        // 自定义纯数字的验证码(随机4位数字,可重复)
        RandomGenerator randomGenerator = new RandomGenerator("0123456789", 4);
        LineCaptcha lineCaptcha = cn.hutool.captcha.CaptchaUtil.createLineCaptcha(200, 100);
        lineCaptcha.setGenerator(randomGenerator);
        // 重新生成code
        lineCaptcha.createCode();
        return lineCaptcha.getCode();
    }
}

然后就是redis缓存的时候,key要标准化,要有前缀区分,就用手机号,然后是业务区分,就用验证码

@Service
public class VerificationCodeServiceImpl implements VerificationCodeService {
    private static final String VERIFICATION_CODE_PREFIX = "verificationCode_CODE_";
    private static final Long VERIFICATION_CODE_TIMEOUT =60L;
    @Override
    public void sendVerificationCode(String phone) {
        //校验手机号
        if (!RegexUtil.checkMobile(phone)) {
            throw new ServiceException(ServiceErrorCodeConstants.PHONE_NUMBER_ERROR);
        }
        //生成随机验证码
        String code = CaptchaUtil.getCaptcha(4);
        //发送验证码
//        Map<String, String> map = new HashMap<>();
//        map.put("code", code);
//        smsUtil.sendMessage("SMS_465324787", phone, JacksonUtil.writeValueAsString(map));
        //第一个是模版id,第二个是手机号,第三个是模版参数JSON串
        //缓存验证码
        RedisUtil.set(VERIFICATION_CODE_PREFIX+phone, code, VERIFICATION_CODE_TIMEOUT);
    }

    @Override
    public String getVerificationCode(String phone) {
        //校验手机号
        if (!RegexUtil.checkMobile(phone)) {
            throw new ServiceException(ServiceErrorCodeConstants.PHONE_NUMBER_ERROR);
        }
        return RedisUtil.get(VERIFICATION_CODE_PREFIX+phone);
    }
}

在这里插入图片描述

登录认证方式JWT

1.cookie和session只能在web下,不能跨域,集群环境下失效

2.token方式:
在这里插入图片描述
缺点就是考验redis了

3.基于token的JWT令牌

在这里插入图片描述

JWTUtil

就是生成jwt和解析jwt
先引入依赖

		<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-api</artifactId>
			<version>0.11.5</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl -->
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-impl</artifactId>
			<version>0.11.5</version>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
			<version>0.11.5</version>
			<scope>runtime</scope>
		</dependency>

注意jwt就是jsonwebtoken

public class JWTUtil {
    private static final Logger logger = LoggerFactory.getLogger(JWTUtil.class);
    /**
     * 密钥:Base64编码的密钥
     */
    private static final String SECRET = "SDKltwTl3SiWX62dQiSHblEB6O03FG9/vEaivFu6c6g=";
    /**
     * 生成安全密钥:将一个Base64编码的密钥解码并创建一个HMAC SHA密钥。
     */
    private static final SecretKey SECRET_KEY = Keys.hmacShaKeyFor(
            Decoders.BASE64.decode(SECRET));
    /**
     * 过期时间(单位: 毫秒)
     */
    private static final long EXPIRATION = 60*60*1000;

    /**
     * 生成密钥
     *
     * @param claim  {"id": 12, "name":"张山"}
     * @return
     */
    public static String genJwt(Map<String, Object> claim){
        //签名算法
        String jwt = Jwts.builder()
                .setClaims(claim)             // 自定义内容(载荷)
                .setIssuedAt(new Date())      // 设置签发时间
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION)) // 设置过期时间
                .signWith(SECRET_KEY)         // 签名算法
                .compact();
        return jwt;
    }

    /**
     * 验证密钥
     */
    public static Claims parseJWT(String jwt){
        if (!StringUtils.hasLength(jwt)){
            return null;
        }
        // 创建解析器, 设置签名密钥
        JwtParserBuilder jwtParserBuilder = Jwts.parserBuilder().setSigningKey(SECRET_KEY);
        Claims claims = null;
        try {
            //解析token
            claims = jwtParserBuilder.build().parseClaimsJws(jwt).getBody();
        }catch (Exception e){
            // 签名验证失败
            logger.error("解析令牌错误,jwt:{}", jwt, e);
        }
        return claims;

    }

    /**
     * 从token中获取用户ID
     */
    public static Integer getUserIdFromToken(String jwtToken) {
        Claims claims = JWTUtil.parseJWT(jwtToken);
        if (claims != null) {
            Map<String, Object> userInfo = new HashMap<>(claims);
            return (Integer) userInfo.get("userId");
        }
        return null;
    }
}

还有值得注意的就是秘钥不是随便生成的,必须用特定的方法生成才算

   /**生成密钥*/
    @Test
    public void genKey(){
        // 创建了一个密钥对象,使用HS256签名算法。
        Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
        // 将密钥编码为Base64字符串。
        String secretString = Encoders.BASE64.encode(key.getEncoded());
        System.out.println(secretString);
        // 结果:dVnsmy+SIX6pNptQdeclDSJ26EMSPEIhvZYKBTTug4k=
    }

就是这个方法

生成的秘钥才算真正的秘钥

我们这个秘钥直接就用一个常量了
所以就很OK

总结

内容概要:本文详细介绍了基于Simulink平台构建的锂电池供电与双向DCDC变换器智能切换工作的仿真模型。该模型能够根据锂离子电池的状态荷电(SOC)自动或手动切换两种工作模式:一是由锂离子电池通过双向DCDC变换器向负载供电;二是由直流可控电压源为负载供电并同时通过双向DCDC变换器为锂离子电池充电。文中不仅提供了模式切换的具体逻辑实现,还深入探讨了变换器内部的电压电流双环控制机制以及电池热管理模型的关键参数设定方法。此外,针对模型使用过程中可能遇到的问题给出了具体的调试建议。 适用人群:从事电力电子、新能源汽车、储能系统等领域研究和技术开发的专业人士,尤其是那些希望深入了解锂电池管理系统及其与电源转换设备交互机制的研究者和工程师。 使用场景及目标:适用于需要评估和优化锂电池供电系统的性能,特别是涉及双向DCDC变换器的应用场合。通过学习本文提供的理论知识和实践经验,可以帮助使用者更好地理解和掌握相关技术细节,从而提高实际项目的设计效率和可靠性。 其他说明:为了确保仿真的准确性,在使用该模型时需要注意一些特定条件,如仿真步长限制、电池初始SOC范围以及变换器电感参数的选择等。同时,对于可能出现的震荡发散现象,文中也提供了一种有效的解决办法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值