Web前端基础知识

html:

input

<input type="date">:日期 <input type="datetime">:日期时间(兼容性不是很好) <input type="datetime-local" />日期时间 <input type="time" />时间

outline 可去掉框

<textarea>文本域显示的大小

上传资料:<input type="file" />

Framset:不能和body同时出现,、

Framset标签的作用,使当前页面进行其他页面内容的显示设置,页面本身的body标签失效;有body无框架,有框架无body;

常用属性:rows对多个页面进行“行”布局

          Cols对多个页面进行“列”布局

Eg<frameset rows="30%,*" frameborder="0">

行内样式  在标签里面写

外接样式 在<style>标签里面写

属性选择器 指定元素的属性【属性=属性值】

Id > class > tagname(标记选择器的优先级最低)

f

flex布局

display: flex; 父元素设置 子块元素会排成一行

align-items: center;竖着的

justify-content: center; 横着的

子块元素左右对齐:子块元素设置 margin-?:auto

子元素都设置flex:1  会在一行平分flex:0 1 10% 挨个设置 会按比例占据位置。

设置布局!

.parent {
    display: flex;
    padding: 20px;
}

.child {
    width: 100%;
}
Flexbox会自动处理尺寸的计算  

现在依旧会溢出,必须加上:

* {
	padding: 0;
	margin: 0;
	box-sizing: border-box; /* 这样设置可以避免大部分宽度计算问题 */

}

padding 内边距 上右下左  margin 外边距 想让他上移 margin设置负数

gap : 设置框与框的  间距 gap:20px

flex-wrap:wrap 换行。

文字撑满容器:

text-align: justify;(快淘汰了)

text-align:center

不换行

  • overflow:hidden 隐藏
  • white-space:normal 默认
  • pre 换行和其他空白字符都将受到保护
  • nowrap(在一行显示)

z -index:  当  position不是static时 

居中对齐!

水平 text-align:

垂直 设置行高=容器高度   line-height: 60px;

居于最中间:

/* 设置元素宽度和高度 */
width: 800px;
height: 500px;

/* 将元素定位为绝对定位,相对于其最近的非静态定位的父元素进行定位 */
position: absolute;

/* 将元素的左上角定位在其父元素的50%水平位置 */
left: 50%;
/* 将元素的左上角定位在其父元素的50%垂直位置 */
top: 50%;

/* 通过负的 margin-left 值,使元素的中心点在水平方向上向左移动,实现水平居中 */
margin-left: -400px;

/* 通过负的 margin-top 值,使元素的中心点在垂直方向上向上移动,实现垂直居中 */
margin-top: -250px;

Jscript页面传参

写到storage中

接受其他页面传递的数据

setInterval(
                () => {
                    hh.height = Number(localStorage.getItem("hhh"))+Number(10)
                    console.log(hh.height)
                }, 100)

传递页面参数

    let my = document.getElementById("box");
        window.onload = function() {
            localStorage.setItem("hhh",my.scrollHeight)
        
        }

iframe的间距如何去除?

align="top"

js script放在 <html/>标签后会出现bug

id 写在body中 不能写在 html中

    display: inline-block; 要对 每一个子块元素设置!

flex最后一个元素右对齐

“在使用display:flex的前提下,对项目的最后一个元素进行向右对齐,则使用margin-left:auto(在最后一个元素)即可。

获取时间

        function aa() {
            var time = new Date()
            var oldtime = time.getTime() //获取1970年到现在的毫秒数 
            var time2 = new Date('2024/3/12 18:00:00')
            var newtime = time2.getTime() //获取1970到2024的毫秒数
            var end = Math.floor((newtime - oldtime) / 1000) // 现在到2024的秒数
            console.log("总秒数:", end)
            var day = Math.floor(end / 86400); //总秒数/一天的秒数==几天(保留整数)
            console.log("总天数:", day)
            end = Math.floor(end % 86400) //剩下的秒数
            console.log("剩下的秒数:", end)
            var hour = Math.floor(end / 3600)
            console.log("小时数:", hour)
            var end = Math.floor(end % 3600)
            var min = Math.floor(end / 60)
            console.log("分钟数:", min)
            var second = Math.floor(end % 60)
            console.log("秒数:", second)
            //写到html页面上
            document.getElementById("second").innerText = second
            document.getElementById("day").innerText = day
            document.getElementById("hour").innerText = hour
            document.getElementById("min").innerText = min


        }

<datalist> 标签定义选项列表。请与 input 元素配合使用该元素,来定义 input 可能的值。

datalist 及其选项不会被显示出来,它仅仅是合法的输入值列表。

表单提交的数据 要设置name属性 

下面的可以访问上面的!!! 

设置position:fixd后 高宽度就失效了 就飘起来了 再设置个空盒子 占据它的位置

js实现 同意协议允许注册

function a() {
    var dot = document.getElementById("tongyi")
    var button = document.getElementById("zhuce")
    if (dot.checked === true) {
        button.removeAttribute("disabled")
        console.log("www")
        button.style.background = "red"
    } else {
        button.disabled = "disabled";
        button.style.background = "gray"

    }
}

function b(event) {
    var dot = document.getElementById("tongyi")
    if (dot.checked != true) {
        console.log("哈哈哈")
        event.preventDefault()
        window.confirm("please agree the items")
    }
}


js调用第三方接口

放网址 它会给咱传入参数 json 注意格式转化。

fetch.then是异步操作,里面的操作是无顺序的,所以要再加个.then();


// 2 查询天气
function weather(id) {
    fetch(`https://devapi.qweather.com/v7/weather/now?key=8431c4eef589430284f12e43b6517ec6&location=${id}`).then(res => {
        return res.text()
    }).then(data => {
        var obj = JSON.parse(data)
        // 获取温度
        var wd = document.querySelector(".wd h1 span:nth-child(1)")
        // 将温度赋值给页面
        wd.innerHTML = obj.now.temp
        // 获取天气图标
        var img = document.querySelector(".wd h1 img")
        // 设置图片的地址
        img.src = `icon/${obj.now.icon}.png`

        // 获取设置天气描述数据
        var q = document.querySelector(".qing")
        q.innerHTML = obj.now.text

        // 获取设置湿度数据
        var shidu = document.querySelector(".shidu")
        shidu.innerHTML = obj.now.humidity + "%"
        // 获取设置风向数据
        var fengxiang = document.querySelector(".fengxiang")
        fengxiang.innerHTML = obj.now.windDir
        // 获取设置风速数据
        var dengji = document.querySelector(".dengji")
        dengji.innerHTML = obj.now.windScale + "级"
    })

后端

Mapper

BaseMapper的作用?为什么要继承它

/**
 * BaseMapper 概述
 * 
 * BaseMapper 接口定义了一组通用的方法,可以直接在 Mapper 接口中使用。这些方法包括:
 */


/**
 * Mapper接口
 */
@Mapper
public interface RoleMapper extends BaseMapper<Role> {
}

// Service类
@Service
public class RoleService {

    @Autowired
    private RoleMapper roleMapper;

    /**
     * 插入一条记录
     */
    public void insertRole(Role role) {
        roleMapper.insert(role);
    }

    /**
     * 根据ID删除一条记录
     */
    public void deleteRoleById(Long id) {
        roleMapper.deleteById(id);
    }

    /**
     * 根据ID集合批量删除记录
     */
    public void deleteRolesByIds(List<Long> ids) {
        roleMapper.deleteBatchIds(ids);
    }

    /**
     * 根据ID更新一条记录
     */
    public void updateRoleById(Role role) {
        roleMapper.updateById(role);
    }

    /**
     * 根据ID查询一条记录
     */
    public Role selectRoleById(Long id) {
        return roleMapper.selectById(id);
    }

    /**
     * 根据ID集合批量查询记录
     */
    public List<Role> selectRolesByIds(List<Long> ids) {
        return roleMapper.selectBatchIds(ids);
    }

    /**
     * 根据条件查询记录列表
     */
    public List<Role> selectRolesByRoleKey(String roleKey) {
        QueryWrapper<Role> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("role_key", roleKey);
        return roleMapper.selectList(queryWrapper);
    }

    /**
     * 根据条件查询记录数量
     */
    public int selectRoleCountByRoleKey(String roleKey) {
        QueryWrapper<Role> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("role_key", roleKey);
        return roleMapper.selectCount(queryWrapper);
    }

    /**
     * 根据条件查询单条记录
     */
    public Role selectOneRoleByRoleKey(String roleKey) {
        QueryWrapper<Role> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("role_key", roleKey);
        return roleMapper.selectOne(queryWrapper);
    }

    /**
     * 根据条件分页查询记录
     */
    public IPage<Role> selectRolePage(int pageNum, int pageSize, String roleKey) {
        Page<Role> page = new Page<>(pageNum, pageSize);
        QueryWrapper<Role> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("role_key", roleKey);
        return roleMapper.selectPage(page, queryWrapper);
    }
}
 QueryWrapper<Role> queryWrapper=new QueryWrapper<>();//QueryWrapper用来做比较的!
 queryWrapper.eq("role_key","admin");//eq相等 ge大于等于 等等,具体看baomidou官网

代码生成

Mybatis-PlusX 代码生成后还要在 主启动类中加入 包扫描,这样才能正确运行!必选

@MapperScan("com.ccz.vueshop.mapper")

SQL打印用 p6spy

<dependency>
    <groupId>p6spy</groupId>
    <artifactId>p6spy</artifactId>
    <version>3.9.1</version>
</dependency>
spring:
  profiles:
    active: dev

注意测试环境使用。application-dev

需引入spy.properties文件

#################################################################
# P6Spy 配置文件                                                 #
# 详细使用说明请参考                                           #
# http://p6spy.github.io/p6spy/2.0/configandusage.html          #
#################################################################

#################################################################
# 模块                                                         #
#                                                              #
# modulelist 指定了P6Spy的模块功能。                           #
# 只有列出的模块才会被激活。                                   #
# (默认是 com.p6spy.engine.logging.P6LogFactory 和              #
# com.p6spy.engine.spy.P6SpyFactory)                           #
# 请注意,核心模块 (P6SpyFactory) 不能被禁用。                   #
# 与其他属性不同,这一属性的变化需要重新加载才能生效。           #
#################################################################
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
# 如果使用 3.2.1 之前的版本
# modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory

# 自定义日志格式输出
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger

# 日志输出到控制台
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger

# 使用日志系统记录 SQL
# appender=com.p6spy.engine.spy.appender.Slf4JLogger

# 启用 p6spy driver 代理
deregisterdrivers=true

# 使用带前缀的 JDBC URL
useprefix=true

# 配置日志记录的类别,去除不必要的类别,如:error, info, batch, debug, statement, commit, rollback, result, resultset
excludecategories=info,debug,result,commit,resultset

# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss

# 指定要加载的 JDBC 驱动程序列表
# driverlist=org.h2.Driver

# 是否启用SQL长时间未响应记录
outagedetection=true

# SQL长时间未响应的标准(单位:秒)
outagedetectioninterval=2

相应修改

spring:
  datasource:
    driver-class-name:  com.p6spy.engine.spy.P6SpyDriver
    url: jdbc:p6spy:mysql://localhost:3306/vueshop?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    username: root
    password: 1234

结果集封装

前后端交互的标准

package com.markerhub.common.lang;

import lombok.Data;

import java.io.Serializable;

@Data
public class Result implements Serializable {

	//	结果状态(如200表示成功,400表示异常)
	private int code;
	//	结果消息
	private String msg;
	//	结果数据
	private Object data;

	public static Result success() {
		return success(null);
	}

	public static Result success(Object data) {
		Result result = new Result();
		result.setCode(200);
		result.setMsg("操作成功");
		result.setData(data);
		return result;
	}

	public static Result fail(String msg) {
		Result result = new Result();
		result.setCode(400);
		result.setMsg(msg);
		result.setData(null);
		return result;
	}

}

全局异常处理

不可避免服务器报错的情况,如果不配置异常处理机制,就会默认返回tomcat或者nginx的5XX页面,对普通用户来说

404全局异常处理:

  mvc:
    throw-exception-if-no-handler-found: true
  web:
    resources:
      add-mappings: false

Hibernate validatior 实体校验

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
@TableName("app_user")
public class AppUser implements Serializable {
    
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    
    @NotBlank(message = "昵称不能为空")
    private String username;

    @NotBlank(message = "手机号不能为空")
    private String tel;

    ...
}
/**
 * 测试实体校验
 * @param user
 * @return
 */
@PostMapping("/save")
public Object testSave(@Validated @RequestBody AppUser appUser) {
    return user.toString();
}

异常捕获

// 设置当抛出 BindException 异常时返回的 HTTP 状态码为 400 Bad Request
@ResponseStatus(HttpStatus.BAD_REQUEST)
// 定义一个方法来处理 BindException 异常
@ExceptionHandler(value = BindException.class)
public Result handler(BindException e) {
    // 获取绑定结果(验证结果)
    BindingResult bindingResult = e.getBindingResult();
    // 从绑定结果中获取第一个错误对象
    ObjectError objectError = bindingResult.getAllErrors().stream().findFirst().get();

    // 记录错误日志,包含错误信息
    log.error("实体校验异常 ---- {}", objectError.getDefaultMessage());
    // 返回失败结果,包含错误信息
    return Result.fail(objectError.getDefaultMessage());
}

跨域问题

前后端域名不一样就出现了问题了,限定同域名才可以访问,所以要让它没有这个限制 很固定!在Config里面配置即可。

package com.markerhub.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfig implements WebMvcConfigurer {

	private CorsConfiguration buildConfig() {
		CorsConfiguration corsConfiguration = new CorsConfiguration();
		corsConfiguration.addAllowedOrigin("*");
		corsConfiguration.addAllowedHeader("*");
		corsConfiguration.addAllowedMethod("*");
		corsConfiguration.addExposedHeader("Authorization");
		return corsConfiguration;
	}

	@Bean
	public CorsFilter corsFilter() {
		UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
		source.registerCorsConfiguration("/**", buildConfig());
		return new CorsFilter(source);
	}

	@Override
	public void addCorsMappings(CorsRegistry registry) {
		registry.addMapping("/**")
				.allowedOrigins("*")
//          .allowCredentials(true)
				.allowedMethods("GET", "POST", "DELETE", "PUT")
				.maxAge(3600);
	}
}

JWT登录验证

1.导入包 配置

<!-- jwt -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
<!--redis存登录信息-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
<!--        实体验证-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
<!--        hutool工具类-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.12</version>
        </dependency>
<!--        BouncyCastle 是一个提供加密算法和协议的开源库,只有加了这个Hutool的工具类才能正常加密!-->
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15on</artifactId>
            <version>1.70</version>
        </dependency>
jwt:
  secret: f4e2e52034348f86b67cde581c0f9eb5
  expire: 604800

secret 是一个字符串,用作 JWT 的签名密钥。

expire 是一个长整型数值,表示 JWT 的过期时间,单位为秒.604800 秒(即 7 天)

2.JwtUtils

jwt工具类 格式很固定,copy此版本即可。

package com.ccz.vueshop.utils;
// 定义了包名,用于组织和管理相关的类文件。

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
// 导入 JSON Web Token (JWT) 库中的类,用于处理 JWT。

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
// 导入 Lombok 库中的注解,简化代码,生成日志记录器和 getter/setter 方法。

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
// 导入 Spring 框架中的注解,用于配置类和声明组件。

import java.util.Date;
// 导入 Java 标准库中的 Date 类,用于处理日期和时间。

@Slf4j
// 为类启用日志记录功能。
@Data
// 生成 getter/setter 方法、toString 方法、equals 和 hashCode 方法。
@Component
// 将此类声明为 Spring 组件,使其可以被自动扫描和装配。
@ConfigurationProperties(prefix = "jwt")
// 将此类的属性与配置文件中的 "jwt" 前缀的属性进行绑定。
public class JwtUtils {

    private String secret;
    // JWT 签名的密钥,从配置文件中获取。

    private long expire;
    // JWT 的过期时间,从配置文件中获取,单位为秒。

    /**
     * 生成 JWT token
     */
    public String generateToken(long userId, String issuer) {
        Date nowDate = new Date();
        // 获取当前时间。

        // 过期时间
        Date expireDate = new Date(nowDate.getTime() + expire * 1000);
        // 计算过期时间,当前时间加上配置的过期时长。

        return Jwts.builder()
                .setHeaderParam("typ", "JWT")
                // 设置头部参数,类型为 JWT。
                .setSubject(userId + "")
                // 设置主题,即用户ID。
                .setIssuedAt(nowDate)
                // 设置签发时间。
                .setIssuer(issuer)
                // 设置签发者。
                .setExpiration(expireDate)
                // 设置过期时间。
                .signWith(SignatureAlgorithm.HS512, secret)
                // 设置签名算法和密钥。
                .compact();
                // 生成并压缩 JWT。
    }

    /**
     * 从 JWT token 中获取 Claims
     */
    public Claims getClaimByToken(String token, String issuer) {
        try {
            Claims claims = Jwts.parser()
                    .setSigningKey(secret)
                    // 设置解析 JWT 的签名密钥。
                    .parseClaimsJws(token)
                    // 解析 JWT。
                    .getBody();
                    // 获取其中的 Claims。

            if (issuer == null || !claims.getIssuer().equals(issuer)) {
                // 验证签发者是否一致。
                return null;
            }
            return claims;
            // 返回 Claims。
        } catch (Exception e) {
            log.debug("validate is token error ", e);
            // 记录验证 token 的错误信息。
            return null;
        }
    }

    /**
     * 判断 token 是否过期
     * @return true:过期
     */
    public boolean isTokenExpired(Date expiration) {
        return expiration.before(new Date());
        // 判断过期时间是否在当前时间之前,若是则表示 token 已过期。
    }
}

  • Date:一个较老的类,包含日期和时间,精确到毫秒,不推荐在新代码中使用。
  • LocalDateTime:Java 8 引入的类,表示不带时区的日期和时间,精确到纳秒,推荐使用。
  • JSR310: Date APl  充分利用 Java 8 时间 API 的优势,包括更好的设计、丰富的功能和线程安全性。

3.业务代码

3.1 Service层代码
 @Override
    public AppUser login(LoginDto loginDto) {

        // 根据用户名查询用户信息
        AppUser user = this.getOne(new QueryWrapper<AppUser>().eq("username", loginDto.getUsername()));

        // 断言用户不为空,如果为空则抛出异常,提示用户名或密码错误
        Assert.notNull(user, "用户名或密码错误");

        // 输出登录请求中的加密密码(用于调试)
        System.out.println("加密密码:" + SecureUtil.md5(loginDto.getPassword()));

        // 验证密码是否正确,如果不正确则抛出异常
        if (!user.getPassword().equals(SecureUtil.md5(loginDto.getPassword()))) {
            throw new HubException("用户名或密码错误");
        }

        // 更新用户的最后登录时间为当前时间
        user.setLastLogin(LocalDateTime.now());

        // 根据用户 ID 更新用户信息,
//this:当前类的实例。在这个上下文中,this 表示调用 login 方法的当前对象
        this.updateById(user);

        // 返回用户的 ID
        return user;
    }
3.2Controller 层代码
 @PostMapping("/login")
    public Result login(@Validated @RequestBody LoginDto loginDto) {
        // 校验用户名和密码
        AppUser appUser = appUserService.login(loginDto);

        // 生成 token
        String token = jwtUtils.generateToken(appUser.getId(), "APP");

        // 返回成功结果,包含 token 和用户信息
        return Result.success(MapUtil.builder()
                .put("token", token)
                .put("userInfo", appUser)
                .build());
    }

注册

导入

<!--图片验证码-->
<dependency>
    <groupId>com.github.axet</groupId>
    <artifactId>kaptcha</artifactId>
    <version>0.0.9</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

配置信息:

/**
 * 图片验证码的配置信息
 */
@Configuration
public class KaptchaConfig {
   @Bean
   public DefaultKaptcha producer() {
      Properties properties = new Properties();
      properties.put("kaptcha.border", "no");
      properties.put("kaptcha.textproducer.font.color", "black");
      properties.put("kaptcha.textproducer.char.space", "4");
      properties.put("kaptcha.image.height", "40");
      properties.put("kaptcha.image.width", "120");
      properties.put("kaptcha.textproducer.font.size", "30");
      Config config = new Config(properties);
      DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
      defaultKaptcha.setConfig(config);
      return defaultKaptcha;
   }
}

生成验证码 通过谷歌的工具生成的

@GetMapping("/captcha")
// 注解:将该方法映射为处理 HTTP GET 请求的处理器,URL 路径为 "/captcha"。

public Result getCaptcha() throws IOException {
// 方法定义:返回类型为 Result,方法名为 getCaptcha,抛出 IOException 异常。

    // 生成uuid和验证码
    String uuid = UUID.randomUUID().toString();
    // 生成一个唯一的 UUID 字符串,用作验证码的唯一标识符。

    String code = producer.createText();
    // 生成验证码文本,通常是一个随机生成的字符串。

    log.info("uuid - code - {},{}", uuid, code);
    // 记录日志:打印生成的 UUID 和验证码,用于调试和跟踪。

    // 生成base64编码的图片验证码
    BufferedImage image = producer.createImage(code);
    // 根据验证码文本生成验证码图片。

    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    // 创建一个字节数组输出流,用于存储图片数据。

    ImageIO.write(image, "jpeg", outputStream);
    // 将验证码图片以 JPEG 格式写入字节数组输出流。

    BASE64Encoder encoder = new BASE64Encoder();
    // 创建一个 Base64 编码器,用于将图片数据转换为 Base64 编码字符串。

    String base64Img = "data:image/jpeg;base64," + encoder.encode(outputStream.toByteArray());
    // 将图片数据转换为 Base64 编码字符串,并添加 MIME 类型前缀。

    // 把uuid和验证码存储到redis
    redisUtil.set(uuid, code, 120);
    // 将 UUID 和验证码文本存储到 Redis,设置有效期为 120 秒。

    // 返回uuid和base64图片
    return Result.success(MapUtil.builder()
            .put("uuid", uuid)
            .put("base64Img", base64Img)
            .build());
    // 返回结果:包含 UUID 和 Base64 编码的图片验证码的成功响应。
}

RegisterDto是为了接受前端来的json数据,以及校验,不能用 实体类 ,为了职责分离和降低耦合。同时用了实体校验。

@Data
public class RegisterDto implements Serializable {

   @NotBlank(message="昵称不能为空")
   private String username;
   
   @NotBlank(message="密码不能为空")
   private String password;
   
   @NotBlank(message="验证码错误")
   private String uuid;
   
   @NotBlank(message="验证码错误")
   private String code;
}

现在有了验证码了 进行注册!

@PostMapping("/register")
// 注解:将该方法映射为处理 HTTP POST 请求的处理器,URL 路径为 "/register"。

public Result register(@Validated @RequestBody RegisterDto form){
// 方法定义:返回类型为 Result,方法名为 register,使用 @RequestBody 注解将请求体中的 JSON 数据映射为 RegisterDto 对象,并使用 @Validated 注解进行验证。

   // 验证码校验
   Assert.isTrue(form.getCode().equals(redisUtil.get(form.getUuid())), "验证码错误");
   // 检查验证码是否正确。使用 UUID 获取 Redis 中存储的验证码,并与表单中的验证码进行比较。如果不匹配,抛出异常。

   long count = appUserService.count(new QueryWrapper<AppUser>().eq("username", form.getUsername()));
   // 查询数据库中是否已有相同用户名的用户。使用 MyBatis Plus 的 QueryWrapper 构造查询条件。

   if (count > 0){
      return Result.fail("用户名已存在");
   }
   // 如果数据库中已存在相同用户名的用户,则返回失败结果,提示用户名已存在。

   AppUser user = new AppUser();
   // 创建一个新的 AppUser 实例。

   user.setUsername(form.getUsername());
   // 设置用户的用户名为表单中的用户名。

   user.setPassword(SecureUtil.md5(form.getPassword()));
   // 设置用户的密码为表单中的密码,并使用 MD5 加密。

   user.setCreated(LocalDateTime.now());
   // 设置用户的创建时间为当前时间。

   appUserService.save(user);
   // 将用户信息保存到数据库中。

   log.info("用户注册成功 ---- {}", user.getUsername());
   // 记录日志,提示用户注册成功,并打印用户名。

   return Result.success();
   // 返回成功结果。
}

用户认证Token

登录完成之后,就会返回系统生成的jwt为了身份认证的token,在访问受限资源的时候需要在请求头header中携带token作为身份认证,比如访问用户中心,访问购物车,查看我的订单。这些接口都是需要知道当前用户是谁才能访问的。

app端和system端不一样,权限系统有shiro框架。在App端,我们如何让当前接口设置只有登录用户才能访问呢?这次的解决方案也很简单,我们定义一个@Login注解,在需要登录才能访问的接口添加上这个注解,然后设置一个拦截器拦截这个注解。拦截器中获取用户的jwt,判断是否合法,然后把userId存储到session中,这样在当前会话的操作中都能知道用户是谁了。

<!--        添加 Apache Shiro 依赖-->
<!--        处理身份验证、授权、加密和会话管理等安全相关任务-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.9.1</version>
        </dependency>

1.定义注解

@Target(ElementType.METHOD) // 指定该注解只能用于方法上。
@Retention(RetentionPolicy.RUNTIME) // 指定该注解在运行时可见。
@Documented // 使该注解包含在 Javadoc 中。
public @interface Login { // 定义一个名为 Login 的注解,没有任何属性。
}

拦截器的逻辑其实很简单:

  • 判断访问的方法上是否有Login注解,没有就放行
  • 有token的话就验证jwt是否合法
  • 把userId设置在session中,方便后续流程中需要获取用户Id
    然后还需要把拦截器注入到spring框架中,拦截所有/app开头的链接
  • package com.ccz.vueshop.modules.app.interceptor;
    
    import com.ccz.vueshop.common.lang.Const;
    import com.ccz.vueshop.modules.app.annotation.Login;
    import com.ccz.vueshop.utils.JwtUtils;
    import io.jsonwebtoken.Claims;
    import org.apache.shiro.authz.UnauthenticatedException;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;
    import org.springframework.web.method.HandlerMethod;
    import org.springframework.web.servlet.HandlerInterceptor;
    
    import javax.annotation.Resource;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    @Component
    public class AuthInterceptor implements HandlerInterceptor {
    
        @Resource
        JwtUtils jwtUtils;
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
            // 判断是否有@Login注解,没有就放行
            Login annotaion;
            if (handler instanceof HandlerMethod) {
                annotaion = ((HandlerMethod) handler).getMethodAnnotation(Login.class);
            } else {
                return true;
            }
    
            if (annotaion == null) {
                return true;
            }
    
            // 获取用户凭证token
            String token = request.getHeader("token");
    
            // 判断token是否合法
            if (!StringUtils.hasText(token)) {
                throw new UnauthenticatedException("请先登录");
            }
    
            Claims claim = jwtUtils.getClaimByToken(token, "APP");
            if (claim == null || jwtUtils.isTokenExpired(claim.getExpiration())) {
                throw new UnauthenticatedException("请先登录");
            }
    
            // 把用户信息存到session
            request.getSession().setAttribute(Const.USER_KEY, Long.parseLong(claim.getSubject()));
            return true;
        }
    }
    
    spring:
      redis:
        host: localhost
        port: 6379

    y用到了Redis

  • userId注入到会话session中,那么程序如何获取用户id呢,我在userService中写了这几个方法可以获取:

    @Resource
    HttpSession httpSession;
    
    @Override
    public Long getCurrentUserId() {
       Long userId = (Long) httpSession.getAttribute(Const.USER_KEY);
       return userId == null ? -1 : userId;
    }
    
    @Override
    public AppUser getCurrentUser() {
       Long userId = getCurrentUserId();
       return userId == null ? null : this.getById(userId);
    }

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值