<?xml version="1.0" encoding="UTF-8"?>
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://purl.org/rss/1.0/"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel rdf:about="http://blog.anlucky.cn/index.php/feed/rss/programming/java">
<title>LuckyDu - Java</title>
<link>http://blog.anlucky.cn/index.php/programming/java</link>
<description>Java编程技术</description>
<items>
<rdf:Seq>
<rdf:li resource="http://blog.anlucky.cn/index.php/programming/java/215"/>
<rdf:li resource="http://blog.anlucky.cn/index.php/programming/java/213"/>
<rdf:li resource="http://blog.anlucky.cn/index.php/programming/java/167"/>
<rdf:li resource="http://blog.anlucky.cn/index.php/programming/java/165"/>
<rdf:li resource="http://blog.anlucky.cn/index.php/programming/java/148"/>
<rdf:li resource="http://blog.anlucky.cn/index.php/programming/java/141"/>
<rdf:li resource="http://blog.anlucky.cn/index.php/programming/java/136"/>
<rdf:li resource="http://blog.anlucky.cn/index.php/programming/java/133"/>
<rdf:li resource="http://blog.anlucky.cn/index.php/programming/java/124"/>
<rdf:li resource="http://blog.anlucky.cn/index.php/programming/java/123"/>
</rdf:Seq>
</items>
</channel>
<item rdf:about="http://blog.anlucky.cn/index.php/programming/java/215">
<title>SpringBoot解决全局跨域问题</title>
<link>http://blog.anlucky.cn/index.php/programming/java/215</link>
<dc:date>2024-04-28T11:09:22+08:00</dc:date>
<description>SpringBoot解决全局跨域问题什么是跨域跨域：指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的，是浏览器对javascript施加的安全限制。例如：a页面想获取b页面资源，如果a、b页面的协议、域名、端口、子域名不同，所进行的访问行动都是跨域的，而浏览器为了安全问题一般都限制了跨域访问，也就是不允许跨域请求资源。注意：跨域限制访问，其实是浏览器的限制。理解这一点很重要！！！同源策略：是指协议，域名，端口都要相同，其中有一个不同都会产生跨域；同源策略同源，就是咱们域名、端口号、ip、采用的协议都相同，那么我们就是同源的反之就是不同源的！！！出于浏览器的同源策略限制。同源策略（Sameoriginpolicy）是一种约定，它是浏览器最核心也最基本的安全功能，如果缺少了同源策略，则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的，浏览器只是针对同源策略的一种实现。所以，用最简单的话来说，就是前端可以发请求给服务器，服务器也可以进行响应，只是因为浏览器会对请求头进行判断，所以要么前端设置请求头，要么后端设置请求头JAVA解决CORS跨域请求的方式返回新的CorsFilter重写 WebMvcConfigurer使用注解 @CrossOrigin手动设置响应头 (HttpServletResponse)自定web filter 实现跨域注意:CorFilter / WebMvConfigurer / @CrossOrigin 需要 SpringMVC 4.2以上版本才支持，对应springBoot 1.3版本以上上面前两种方式属于全局 CORS 配置，后两种属于局部 CORS配置。如果使用了局部跨域是会覆盖全局跨域的规则，所以可以通过 @CrossOrigin 注解来进行细粒度更高的跨域资源控制。其实无论哪种方案，最终目的都是修改响应头，向响应头中添加浏览器所要求的数据，进而实现跨域这里只介绍全局解决SpringBoot跨域问题，使用返回新的CorsFilter方式1. 编写CorsConfig.java 类/**
 * 解决全局跨域问题
 */
@Configuration
public class CorsConfig {
    @Bean
    public CorsFilter corsFilter() {

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

        // 配置跨域
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        // 允许哪个请求头
        corsConfiguration.addAllowedHeader(&quot;*&quot;);
        // 允许哪个方法进行跨域
        corsConfiguration.addAllowedMethod(&quot;*&quot;);
        // 允许哪个请求来源进行跨域
        // corsConfiguration.addAllowedOrigin(&quot;*&quot;);
        corsConfiguration.addAllowedOriginPattern(&quot;*&quot;);
        // 是否允许携带cookie进行跨域
        corsConfiguration.setAllowCredentials(true);

        source.registerCorsConfiguration(&quot;/**&quot;,corsConfiguration);

        return new CorsFilter(source);
    }
}2. 全局拦截器配置/**
 * 拦截器
 */
@Configuration
public class WebConfigure implements WebMvcConfigurer {
    /**
     * 全局解决跨域问题
     * @param registry
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping(&quot;/**&quot;)
                .allowedOriginPatterns(&quot;*&quot;)
                .allowedMethods(&quot;GET&quot;, &quot;HEAD&quot;, &quot;POST&quot;, &quot;PUT&quot;, &quot;DELETE&quot;, &quot;OPTIONS&quot;)
                .allowCredentials(true)
                .maxAge(3600)
                .allowedHeaders(&quot;*&quot;);
    }
}
如此，这样，即解决所有解决跨域问题，不外乎就是解决浏览器拦截问题，要么前端设置请求头，要么后端设置请求头，无论谁设置请求头，浏览器只要放行即可</description>
</item>
<item rdf:about="http://blog.anlucky.cn/index.php/programming/java/213">
<title>Java工具类-通用对象转换为Json字符</title>
<link>http://blog.anlucky.cn/index.php/programming/java/213</link>
<dc:date>2024-04-01T09:41:34+08:00</dc:date>
<description>Java工具类-通用对象转换为Json字符import com.alibaba.fastjson2.JSONObject;

import java.lang.reflect.Field;
import java.text.SimpleDateFormat;

public class JsonObjectUtlis {

    public static &lt;U&gt; JSONObject processEntityToJson(Class&lt;U&gt; clazz, U cla) {
        //将传过来的对象进行赋值处理，
        //此时u可用来代表传过来的对象（本示意中是Users）,
        //此时可以用u调用传过来对象的方法
        U u = clazz.cast(cla);
        //以下是验证此示意中实体类可被操作了
        //getDeclaredFields()：获得某个类的所有声明的字段，即包括public、private和proteced，但是不包括父类的申明字段。
        //.getClass()是一个对象实例的方法，只有对象实例才有这个方法，具体的类是没有的
        JSONObject jsonObject = new JSONObject();
        for (Field field : u.getClass().getDeclaredFields()) {
            //允许获取实体类private的参数信息 field.setAccessible(true);
            field.setAccessible(true);
            try {
                String name = field.getType().getName();
                if (&quot;java.util.Date&quot;.equals(name) &amp;&amp; field.get(u) != null) {
                    String dateStr = new SimpleDateFormat(&quot;yyyy-MM-dd HH:mm:ss&quot;).format(field.get(u));
                    if (dateStr.indexOf(&quot;00:00:00&quot;) &gt; 0){
                        dateStr = dateStr.substring(0,10) ;
                    }
                    jsonObject.put(field.getName(), dateStr);
                }else {
                    jsonObject.put(field.getName(), field.get(u));
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
                jsonObject.put(&quot;convertJsonStatus&quot;,500);
                return jsonObject;
            }
        }
        return jsonObject;
    }
}使用例子        JSONObject jsonObject = JsonObjectUtlis.processEntityToJson(JudgeResultSynchronizationMatVO.class, synchronizationVO);
        // 第一个参数为对象的Clss类    第二个入参为实际对象Json判断判断当前字符串是不是Json字符串，是JSON返回True 否则返回falsepublic static boolean isjson(String str) {
        try {
            JSONObject jsonStr = JSONObject.parseObject(str);
            return  true;
        } catch (Exception e) {
            return false;
        }
}</description>
</item>
<item rdf:about="http://blog.anlucky.cn/index.php/programming/java/167">
<title>Java浅拷贝和深拷贝</title>
<link>http://blog.anlucky.cn/index.php/programming/java/167</link>
<dc:date>2023-05-27T20:21:57+08:00</dc:date>
<description>浅拷贝和深拷贝参考资料：Java深拷贝和浅拷贝 - 掘金 (juejin.cn)浅拷贝与深拷贝 - 掘金 (juejin.cn)4.1 什么是浅拷贝？什么是深拷贝？浅拷贝：浅拷贝是创建一个新对象，这个对象有着原始属性值的一份精确拷贝，如果是基本数据类型，拷贝的就是基本数据类型的值，如果是深拷贝，拷贝的就是内存地址。所以如果浅拷贝的对象或者对象本身改变了属性或者值那么也会影响另一个对象深拷贝：深拷贝是将这个对象完整的拷贝下来，从内存空间中开辟一个新的地址来存放这个新对象，所以深拷贝创建的对象在改变值之后不会对另一个对象产生影响浅拷贝只复制指向某个对象的指针，而不复制对象本身，新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象，新对象跟原对象不共享内存，修改新对象不会改到原对象。4.2 赋值和深拷贝，浅拷贝的区别？赋值：当我们把一个对象赋值给另一个变量引用时，会把这个对象在栈内存中的地址给这个变量引用，这两个对象使用 == 进行比较返回true，这个情况下，不管哪个对象发生改变，改变的都是存储空间中同一块内存地址的数据，所以他们是联动的，不管改变哪个对象，另外一个对象的数据也会跟着改变。浅克隆代码演示准备实体类对象，并创建对应的get set 有参数构造和无参数构造并实现Cloneable接口中的clone方法创建学生实体类public class Student implements Cloneable{
    private int sid;
    private String sname;
    private Teacher teacher;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}创建老师实体类public class Teacher implements Cloneable{
    private Integer tid;
    private String tname;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}创建测试类Mainpublic class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        // 创建老师对象
        Teacher teacher = new Teacher();
        teacher.setTid(1);
        teacher.setTname(&quot;张老师&quot;);
        // 学生对象
        Student student = new Student();
        student.setSid(1);
        student.setSname(&quot;小明同学&quot;);
        student.setTeacher(teacher);

        // 创建学生的克隆对象   浅克隆
        Student cloneStudent = (Student) student.clone();

        System.out.println(&quot;浅克隆=》比较源对象和克隆对象  &quot; + (student == cloneStudent));
        System.out.println(&quot;浅克隆=》查询修改前源对象：&quot; + student);
        System.out.println(&quot;浅克隆=》查询修改前克隆对象：&quot; + cloneStudent);

        System.out.println(&quot;修改源对象中学生的sid属性为 2&quot;);
        student.setSid(2);
        System.out.println(&quot;修改源对象中学生的sname属性为 小红同学&quot;);
        student.setSname(&quot;小红同学&quot;);
        System.out.println(&quot;修改源对象中学生的teacher属性中的tid属性为 2&quot;);
        student.getTeacher().setTid(2);
        System.out.println(&quot;浅克隆=》查询修改后源对象：&quot; + student);
        System.out.println(&quot;浅克隆=》查询修改后克隆对象：&quot; + cloneStudent);
    }
}输出结果浅克隆=》比较源对象和克隆对象  false
浅克隆=》查询修改前源对象：Student(sid=1, sname=小明同学, teacher=Teacher(tid=1, tname=张老师))
浅克隆=》查询修改前克隆对象：Student(sid=1, sname=小明同学, teacher=Teacher(tid=1, tname=张老师))
修改源对象中学生的sid属性为 2
修改源对象中学生的sname属性为 小红同学
修改源对象中学生的teacher属性中的tid属性为 2
浅克隆=》查询修改后源对象：Student(sid=2, sname=小红同学, teacher=Teacher(tid=2, tname=张老师))
浅克隆=》查询修改后克隆对象：Student(sid=1, sname=小明同学, teacher=Teacher(tid=2, tname=张老师))浅克隆总结浅拷贝：拷贝前后对象的基本数据类型互不影响，但拷贝前后对象的引用类型因共享同一块内存，会相互影响。浅拷贝是创建一个新对象，这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型，拷贝的就是基本类型的值，如果属性是引用类型，拷贝的就是内存地址 ，所以如果其中一个对象改变了这个地址，就会影响到另一个对象。深克隆代码演示深拷贝：从堆内存中开辟一个新的区域存放新对象，对对象中的子对象进行递归拷贝,拷贝前后的两个对象互不影响。创建学生实体类public class Student implements Cloneable{
    private int sid;
    private String sname;
    private Teacher teacher;
    
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return new Student(sid,sname,new Teacher(teacher.getTid(),teacher.getTname()));
    }
}创建老师实体类public class Teacher implements Cloneable{
    private Integer tid;
    private String tname;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return new Teacher(tid,this.tname);
    }
}创建测试类public class Main2 {
    public static void main(String[] args) throws CloneNotSupportedException {
        // 创建老师对象
        Teacher teacher = new Teacher();
        teacher.setTid(1);
        teacher.setTname(&quot;张老师&quot;);
        // 学生对象
        Student student = new Student();
        student.setSid(1);
        student.setSname(&quot;小明同学&quot;);
        student.setTeacher(teacher);

        // 创建学生的克隆对象   浅克隆
        Student cloneStudent = (Student) student.clone();

        System.out.println(&quot;深克隆=》比较源对象和克隆对象  &quot; + (student == cloneStudent));
        System.out.println(&quot;深克隆=》比较源对象和克隆对象的自定义引用数据类型  &quot; + (student.getTeacher() == cloneStudent.getTeacher()));
        System.out.println(&quot;深克隆=》查询修改前源对象：&quot; + student);
        System.out.println(&quot;深克隆=》查询修改前克隆对象：&quot; + cloneStudent);

        System.out.println(&quot;修改源对象中学生的sid属性为 2&quot;);
        student.setSid(2);
        System.out.println(&quot;修改源对象中学生的sname属性为 小红同学&quot;);
        student.setSname(&quot;小红同学&quot;);
        System.out.println(&quot;修改源对象中学生的teacher属性中的tid属性为 2&quot;);
        student.getTeacher().setTid(2);
        System.out.println(&quot;深克隆=》查询修改后源对象：&quot; + student);
        System.out.println(&quot;深克隆=》查询修改后克隆对象：&quot; + cloneStudent);
    }
}输出结果深克隆=》比较源对象和克隆对象  false
深克隆=》比较源对象和克隆对象的自定义引用数据类型  false
深克隆=》查询修改前源对象：Student(sid=1, sname=小明同学, teacher=Teacher(tid=1, tname=张老师))
深克隆=》查询修改前克隆对象：Student(sid=1, sname=小明同学, teacher=Teacher(tid=1, tname=张老师))
修改源对象中学生的sid属性为 2
修改源对象中学生的sname属性为 小红同学
修改源对象中学生的teacher属性中的tid属性为 2
深克隆=》查询修改后源对象：Student(sid=2, sname=小红同学, teacher=Teacher(tid=2, tname=张老师))
### 深克隆总结

深拷贝：深拷贝会创建一个新的对象，将源对象的属性值放入到新对象，是创建对象赋值，并不是将引用地址指向原来的对象，所以我们在上面测试代码中可以发现深拷贝后的Teacher对象中的源对象和克隆对象比较时返回false，当一个值做更改的时候也不会影响另外一个对象
</description>
</item>
<item rdf:about="http://blog.anlucky.cn/index.php/programming/java/165">
<title>String,StringBuffer,StringBuilder的区别</title>
<link>http://blog.anlucky.cn/index.php/programming/java/165</link>
<dc:date>2023-05-27T20:19:00+08:00</dc:date>
<description>可变性String是final修饰的，他是一个不可变的类，他在每次创建字符串的时候是新建了一个对象，StringBuffer和StringBuilder是一个可变的类，他在每次更改的时候会在源对象的基础上更改，并不会重新创建对象线程安全性String是一个不可变的类，所以他是一个线程安全的，StringBuffer也是一个线程安全的，他的每一个方法都用了一个Synchronized一个同步关键字修饰，StringBuider不是一个线程安全的，多线程的情况下应该使用StirngBuffer性能方面String是性能最低的，因为他是不可变的类，每次修改都是创建对象，StringBuffer是可变的，每次字符串修改或者拼接的时候他不会重新创建对象，StringBuider的性能是最高的，因为StringBuffer是加了锁的，加锁之后性能方面会有所损耗存储方面String存储在字符串常量池中，StringBuffer和StringBuilder是存储在堆内存中</description>
</item>
<item rdf:about="http://blog.anlucky.cn/index.php/programming/java/148">
<title>SpringSecurity安全框架基础入门（前后端分离）</title>
<link>http://blog.anlucky.cn/index.php/programming/java/148</link>
<dc:date>2023-05-16T14:09:00+08:00</dc:date>
<description>SpringSecurity安全框架基础入门（前后端分离）1. 环境配置SpringBoot 2.7.11Maven 3.6.12. 依赖包        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
        &lt;/dependency&gt;

        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt;
            &lt;scope&gt;test&lt;/scope&gt;
        &lt;/dependency&gt;


        &lt;dependency&gt;
            &lt;groupId&gt;com.baomidou&lt;/groupId&gt;
            &lt;artifactId&gt;mybatis-plus-boot-starter&lt;/artifactId&gt;
            &lt;version&gt;3.5.3.1&lt;/version&gt;
        &lt;/dependency&gt;


        &lt;dependency&gt;
            &lt;groupId&gt;org.projectlombok&lt;/groupId&gt;
            &lt;artifactId&gt;lombok&lt;/artifactId&gt;
        &lt;/dependency&gt;


        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-data-redis&lt;/artifactId&gt;
        &lt;/dependency&gt;


        &lt;dependency&gt;
            &lt;groupId&gt;io.jsonwebtoken&lt;/groupId&gt;
            &lt;artifactId&gt;jjwt&lt;/artifactId&gt;
            &lt;version&gt;0.9.1&lt;/version&gt;
        &lt;/dependency&gt;

        &lt;dependency&gt;
            &lt;groupId&gt;mysql&lt;/groupId&gt;
            &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;
            &lt;version&gt;5.1.49&lt;/version&gt;
        &lt;/dependency&gt;


        &lt;dependency&gt;
            &lt;groupId&gt;com.alibaba&lt;/groupId&gt;
            &lt;artifactId&gt;druid-spring-boot-starter&lt;/artifactId&gt;
            &lt;version&gt;1.2.16&lt;/version&gt;
        &lt;/dependency&gt;

        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-security&lt;/artifactId&gt;
        &lt;/dependency&gt;3. 数据库配置CREATE DATABASE /*!32312 IF NOT EXISTS*/`school_secrity` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;

USE `school_secrity`;

/*Table structure for table `user` */

DROP TABLE IF EXISTS `user`;

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT &#039;用户ID&#039;,
  `username` varchar(20) NOT NULL COMMENT &#039;用户名&#039;,
  `password` varchar(512) NOT NULL COMMENT &#039;用户密码&#039;,
  `roler` char(2) NOT NULL DEFAULT &#039;0&#039; COMMENT &#039;0 普通用户&#039;,
  `isDelete` char(1) DEFAULT &#039;0&#039; COMMENT &#039;0 存在 1删除&#039;,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;4. SpringBoot配置文件server:
  port: 8080
  servlet:
    context-path: /
# mybatis plus配置
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # 日志输出
  global-config:
    db-config:
      logic-delete-field: isDelete # 逻辑删除 需要加一个注解 @TableLogic
      logic-not-delete-value: 0 # 未删除逻辑值
      logic-delete-value: 1 # 已经删除逻辑值
# spring 配置
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver # 数据库连接驱动
    url: jdbc:mysql://localhost:3306/school_secrity # 数据库Url
    username: root # 数据库账号
    password: 123456 # 数据库密码
  thymeleaf:
    cache: false # 不使用缓存

  redis:
    host: 192.168.244.128
    port: 63795. 认证流程1. 使用之前的配置若不理解 JWT是什么可以先去了解下 JWT是什么再来看本篇文章在使用之前，首先要关闭 spring security 默认的登录页面因为是前后端分离，我们就可以不使用session域进行存储用户的登录态，这里使用的 JWT认证登录态因为是前后端分离，所以我们就可以在登录成功之后使用 JWT给用户返回一个登录的令牌，来认证登录信息然后我们要指定我们自己的登录接口，和使用spring security自带的一个密码加密对象所以就有了如下的配置文件@Configuration
public class SecrityConfig extends WebSecurityConfigurerAdapter {
    
    /**
     * 密码加密对象
     *
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    
    /**
     * 前后端分离项目,配置不使用Session 和 关闭 csrf 和 指定登录接口
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable() // 关闭scrf
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)// 不使用Session
                .and()
                .authorizeRequests()
                .antMatchers(&quot;/user/login&quot;).anonymous() // 登录接口可以匿名访问
                .anyRequest().authenticated(); // 其他接口必须已经登录


        // 配置认证过滤器 在这里可以先忽略这个
        // http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
    }


    @Bean
    @Override  // 身份验证管理器
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}2. 编写认证实体类在spring security中认证有专门的实体类封装数据，所以我们需要将实体类编写，这里需要实现接口UserDetails即有了如下实体类/**
 * 创建用户登录的对象
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser implements UserDetails {
    
    /**
     * 登录的用户对象
     * 将我们自己编写的表对象实体注入到里面
     */
    private User user;

    /**
     * 权限列表，这里认证流程可以忽略
     * @return
     */
    @Override
    public Collection&lt;? extends GrantedAuthority&gt; getAuthorities() {
        return null;
    }

    /**
     * 获取密码
     * @return
     */
    @Override
    public String getPassword() {
        return user.getPassword();
    }

    /**
     * 获取用户名
     * @return
     */
    @Override
    public String getUsername() {
        return user.getUsername();
    }

    /**
     * 账户是未过期的
     * @return
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * 账户是未被锁定的
     * @return
     */
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /**
     * 凭据是未过期的
     * @return
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * 账户已启用的
     * @return
     */
    @Override
    public boolean isEnabled() {
        return true;
    }
}3. 使用MybatisPlus生成Service和Mapper和表实体类这里忽略不讲，使用逆向工程生成，或使用插件生成，或自己编写都可以4.重写默认登录接口在SpriingSecurity中有一个默认的登录接口，使用的是他自己的用户名和密码，我们需要重写，编程查询数据库得到用户名和密码所以我们的Service层或单独的一个登录接口中要实现接口UserDetailsService这里使用的是MybatisPlus生成的Service,在生成的Service中又实现了接口UserDetailsService 可以根据自己的习惯进行更改所以就有了如下实现类@Service
public class UserServiceImpl extends ServiceImpl&lt;UserMapper, User&gt;
    implements UserService, UserDetailsService {
    /**
     * 重写登录接口
     * @param username
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        System.out.println(&quot;执行重写登录接口&quot;);
        LambdaQueryWrapper&lt;User&gt; wrapper = new LambdaQueryWrapper&lt;&gt;();
        wrapper.eq(User::getUsername,username);
        // 数据库查询用户对象
        User user = baseMapper.selectOne(wrapper);

        if (Objects.isNull(user)){
            throw new RuntimeException(&quot;用户名或者密码错误&quot;);
        }
        
        return new LoginUser(user);
    }
}5. 编写自己的Controller和Service业务层我们在SecrityConfig配置文件中声明了自己的Controller接口请求URL/user/login 所以我们在定义的时候也要写这样的URL，所以有了如下的Controller类@RestController
@RequestMapping(&quot;/user&quot;)
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping(&quot;/login&quot;)
    public Result login(@RequestBody User user){
        Result login = userService.login(user);
        return login;
    }
}Service层中添加代码在接口中需要自己添加此方法的接口    @Override
    public Result login(User user) {
        System.out.println(&quot;进入Login&quot;);
        // 将前端传输过来的用户对象封装成Springsecurity可以使用的对象
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword());
        
        // 认证管理器，在配置文件中注释掉了，可以放开，并且添加
        Authentication authenticate = null;
        try {
            authenticate = authenticationManager.authenticate(authenticationToken);
        }catch (Exception e){
            String message = e.getMessage();
            System.out.println(&quot;message = &quot; + message);
        }


        System.out.println(&quot;authenticate = &quot; + authenticate);
        if (Objects.isNull(authenticate)){
            throw new RuntimeException(&quot;用户名或者密码错误&quot;);
        }
        // 通过DeBug看 authenticate.getPrincipal(); 本质就是我们重写之后的LoginUser类对象，所以可以强转
        System.out.println(&quot;认证通过&quot;);
        LoginUser principal = (LoginUser) authenticate.getPrincipal();
        Map&lt;String, Object&gt; map = new HashMap&lt;&gt;();
        map.put(&quot;id&quot;,principal.getUser().getId());
        // 生成用户的JWT token
        String jwtToken = JWTUtils.getJwtToken(map);
        map.clear();
        map.put(&quot;token&quot;,jwtToken);
        return new Result(200,&quot;成功&quot;,map);
    }6.注意事项其中我们要注意我们的密码加密对象passwordEncoder在此框架中，我们只需要将此对象注入即可以实现在密码校验上的自动校验，但是在密码存储时，需要我们自己调用encoder方法进行加密后存储7. 认证过滤器我们的登录接口是被配置文件放行的，所以我们可以直接访问，直接进入登录接口进行登录，若我们想要使用JWT生成的令牌进行登录态验证，我们就需要配置一个认证的过滤器，这个过滤器是在每次调用service层前执行的，所以就可以对我们每次请求过来的令牌进行认证，用户是否登录所以就有如下Java代码/**
 * JWT过滤器
 */
@Component
public class JwtFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
         // 从请求头中获取token
        String token = request.getHeader(&quot;token&quot;);
        if (Strings.hasText(token)){
            try {
                Jws&lt;Claims&gt; claimsJws = JWTUtils.parseJwtToken(token);
                Object id = claimsJws.getBody().get(&quot;id&quot;);
                System.out.println(&quot;用户的id = &quot; + id);
                // 通过用户ID去redis缓存中拿用户的信息

                // 若用户不存在，那么是未登录 存在则已经登录

                // 封装用户信息
                UsernamePasswordAuthenticationToken authenticationToken
                        = new UsernamePasswordAuthenticationToken(&quot;admin&quot;,&quot;123456&quot;,null);
                // 其中这里的admin 123456 是需要从redis中获取到的数据装进去的并不是固定的
                // 放入上下文对象
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);

            } catch (SignatureException e) {
                e.printStackTrace();
            }
        }
        // 放行
        filterChain.doFilter(request,response);
    }
}OncePerRequestFilter 我们实现这个接口之后，这个过滤器是只被执行一次的，因为过滤器在特殊情况下，是可能执行多次的6. 认证代码执行顺序前端请求控制器，因有过滤器的缘故，会直接进入过滤器进入过滤器，因我们是登录接口，是没有token信息的，那么我们在过滤器中判断没有取到token将直接放行请求去service层登录接下来会正式进入controller中，执行我们的代码逻辑，从controller中执行到我们的service层进入service层，会调用我们自己的login方法，然后方法中会将我们的前端传输过来的用户对象封装成spring security可以识别的用户对象然后使用我们的认证管理器对象，进行认证管理，没有抛出异常证明认证没有错误，我们需要调用认真管理器对象中的方法获取到我们的认证对象的信息接下来就是判断对象是否为null 然后对我们的对象进行简单的封装，使用 JWT生成令牌并且返回在之后的每次请求中需要带入令牌进行请求7. 用户授权首选需要在登录的基础上进行授权，授权目的是判断用户是否有权限进行操作这个接口，或是否有权限进行这个操作在上面的基础上我们在SecurityConfig配置文件中的类上加上注解，开启注解方式的授权@EnableGlobalMethodSecurity(prePostEnabled = true)//开启注解形式的授权功能1. 简单的实现授权在controller中新创建一个test的控制器，有如下代码    @RequestMapping(&quot;/test&quot;)
    @PreAuthorize(&quot;hasAnyAuthority(&#039;user&#039;)&quot;) // 判断当前是否有user权限
    public Object test(){
        return &quot;123123123&quot;;
    }我们去调用这个方法，并且带入我们的token会提示我们没有权限，那么我们就实现了一个简单的权限添加接下来是如何给用户添加权限2. 给用户添加权限我们在编写用户登录的实体类中，我们有一个获取用户权限的方法getAuthorities，所以我们要使用这个方法，将用户的权限封装到这里，即就可以更改LoginUser对象，加入一个属性    /**
     * 用户的权限集合
     */
    private Set&lt;String&gt; set;
    //LoginUserDetails要被存储到redis 但redis要求使用的数据必须序列化 但SimpleGrantedAuthority类型不是序列化
    //需要忽略此属性
    @JSONField(serialize = false)
    private List&lt;SimpleGrantedAuthority&gt; authorities; // 用于封装
     // 构造方法需要写一个不带authorities 只有 set 和 user 两个对象的构造
    public LoginUser(User user, Set&lt;String&gt; set) {
        this.user = user;
        this.set = set;
    }3. 封装权限获取方法    /**
     * @return
     */
    @Override
    public Collection&lt;? extends GrantedAuthority&gt; getAuthorities() {
        if (list!=null){
            return list;
        }
        list = new ArrayList&lt;&gt;();
        for (String s : set) {
            list.add(new SimpleGrantedAuthority(s));
        }
        return list;
    }4. 修改过滤器中代码我们将对象中添加的权限信息，那么在每次请求非登录接口时都需要带上我们的权限，让我们的权限认证系统去认证是否有权限继续执行那么将在过滤器中修改如下代码    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        log.info(&quot;进入过滤器&quot;);
        // 从请求头中获取token
        String token = request.getHeader(&quot;token&quot;);
        if (Strings.hasText(token)){
            try {
                Jws&lt;Claims&gt; claimsJws = JWTUtils.parseJwtToken(token);
                Object id = claimsJws.getBody().get(&quot;id&quot;);
                System.out.println(&quot;用户的id = &quot; + id);
                // 通过用户ID去redis缓存中拿用户的信息

                // 若用户不存在，那么是未登录 存在则已经登录

                // 封装用户信息
                // 获取用户权限信息，这里需要查询数据库获取
                ArrayList&lt;SimpleGrantedAuthority&gt; list = new ArrayList&lt;&gt;();
                list.add(new SimpleGrantedAuthority(&quot;test&quot;));
                list.add(new SimpleGrantedAuthority(&quot;user&quot;));
                UsernamePasswordAuthenticationToken authenticationToken
                        = new UsernamePasswordAuthenticationToken(&quot;admin&quot;,&quot;123456&quot;,list);
                // 放入上下文对象
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);

            } catch (SignatureException e) {
                e.printStackTrace();
            }
        }
        // 放行
        filterChain.doFilter(request,response);
    }8. 自定义异常处理1. 认证失败处理器/**
 * 认证失败异常处理
 */
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse
            response, AuthenticationException authException) throws IOException,
            ServletException {
        Result result= new Result(401, &quot;用户认证失败！&quot;, null);
        response.getWriter().println(result);
    }
}2. 授权失败处理器/**
 * 授权失败异常处理
 */
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        Result result = new Result(400, &quot;授权失败&quot;, null);
        response.getWriter().println(result);
    }
}3. 更改配置类在上面两个异常处理器处理完成后我们需要将异常处理类的对象注入到security的配置文件中，然后就配置完成了@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Autowired
private AuthenticationEntryPointImpl authenticationEntryPointImpl;

//配置异常处理器 这段代码在configure方法中定义
http.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPointImpl)//认证
.accessDeniedHandler(accessDeniedHandlerImpl);//授权
9. 配置跨域设置配置类@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
            //设置允许跨域的路径
        registry.addMapping(&quot;/**&quot;)
                .allowedOriginPatterns(&quot;*&quot;)//允许跨域的域名
                .allowCredentials(true)//允许携带cookie
                .allowedMethods(&quot;GET&quot;,&quot;DELETE&quot;,&quot;PUT&quot;,&quot;POST&quot;)//跨域的请求方式
                .allowedHeaders(&quot;*&quot;)//允许的header属性
                .maxAge(3600);//跨域时间
    }
}然后在security中开启跨域，只需要在configure方法中添加如下代码//springsecrity开启允许跨域
    http.cors();</description>
</item>
<item rdf:about="http://blog.anlucky.cn/index.php/programming/java/141">
<title>SpringBoot基础入门</title>
<link>http://blog.anlucky.cn/index.php/programming/java/141</link>
<dc:date>2023-05-13T16:19:00+08:00</dc:date>
<description>SpringBoot基础入门Spring Boot是由Pivotal团队提供的一套开源框架，可以简化spring应用的创建及部署。它提供了丰富的Spring模块化支持，可以帮助开发者更轻松快捷地构建出企业级应用。Spring Boot通过自动配置功能，降低了复杂性，同时支持基于JVM的多种开源框架，可以缩短开发时间，使开发更加简单和高效。（SpringBoot简化开发，可以快速部署SSM项目，整合了很多个配置，使用的时候只需要加上 Jar 包和对应的配置注解即可实现配置，Springboot内置了Tomcat）1. 构建SpringBoot项目这里使用的IDEA编辑器，每个编辑器的构建方式可能有些不同，但是大致相同，使用别的编辑器的同学可以拿这篇文章作为参考方式一：使用官网构建使用官网页面，直接下载springboot项目，使用流程如下官网： https://start.spring.io/方式二：使用IDEA构建在IDEA中点击new project 创建新项目，然后根据需求构建项目注意：​    使用此方法构建的SpringBoot和方式一构建的方式不同点在于一个是下载了一个压缩包文件，一个是直接在IDEA开发工具中下载，区别不大方式三：自己配置自己配置SpringBoot对于新手来说不太友好，要求对Jar包的熟悉程度比较高，个人不太推荐，大佬忽略，这里没有具体的配置方式配置方式：​    使用IDEA工具构建一个新的Maven工程，然后在POM文件中添加SpringBoot的相关依赖，自己创建Application启动类和配置文件注意：
    Mybatis-Plus不可以直接勾选Jar包进行依赖引入，需要我们自己去官网选择版本引入目录结构2. SpringBoot核心配置SpringBoot有两种配置文件结尾，application.properties application.ymlyml 是一种 yaml 格式的配置文件，主要采用一定的空格、换行等格式排版进行配置。yaml 是一种直观的能够被计算机识别的的数据序列化格式，容易被人类阅读，yaml 类似于 xml，但是语法比 xml 简洁很多，值与前面的冒号配置项必须要有一个空格， yml 后缀也可以使用 yaml 后缀。注意：​    SpringBoot项目的配置文件必须使用application名称，否则不识别1. SpringBoot基本配置# SpringBoot的核心配置文件
server:
  port: 8080  # 指定项目运行的端口
  servlet:
    context-path: /  # 指定项目的运行路径
    encoding:
      charset: utf-8 # 指定项目的编码集
spring:
  profiles:
    active: dev # 多环境下使用的环境是哪个，不写就默认 application2. 多环境配置在实际开发的过程中，我们的项目会经历很多的阶段（开发-&gt;测试-&gt;上线），每个阶段的配置也会不 同，例如：端口、上下文根、数据库等，那么这个时候为了方便在不同的环境之间切换，SpringBoot 提供了多环境配置为每个环境创建一个配置文件，命名必须以 application-环境标识.properties|yml例如：application-dev.ymlapplication-test.yml3. 自定义配置SpringBoot 的核心配置文件中，除了使用内置的配置项之外，我们还可以在自定义配置，然后采用注解去读取配置的属性值如：​    在application.yml配置文件中有如下配置school:
  name: 清华大学
  address: 北京那么我们就可以在项目中使用    @Value(&quot;${school.name}&quot;) // 注入属性
    private String schoolName; 3. SpringBoot事务管理Spring Boot 使用事务非常简单，底层依然采用的是 Spring 本身提供的事务管理在入口类中使用注解 @EnableTransactionManagement 开启事务支持在访问数据库的 Service 方法上添加注解 @Transactional 即可4. SpringBoot整合mybatisPlus、thymeleaf、druid、redis、Dubbo配置# SpringBoot的核心配置文件
server:
  port: 8080  # 指定项目运行的端口
  servlet:
    context-path: /  # 指定项目的运行路径
    encoding:
      charset: UTF-8 # 指定项目的编码集
      enabled: true # 开启为 true
      force: true # 强制使用

# Spring 配置
spring:
  application:
    name: demo # spring项目名称
  profiles:
    active: dev # 使用的环境是哪个
  thymeleaf:
    cache: false # thymeleaf 不使用缓存
  datasource: # 数据库连接配置
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/ssm
    type: com.alibaba.druid.pool.DruidDataSource # 使用的数据库连接池需要导入 jar 包 druid-spring-boot-starter
    druid: # 可以不配置，有默认值
      initial-size: 5 # 初始化连接数
      min-idle: 5 # 最小连接数
      max-active: 20 # 最大连接数
      max-wait: 60000 # 获取数据库连接最大等待时间 单位毫秒
      time-between-eviction-runs-millis: 60000 # 配置多久进行一次检测，检测需要关闭的空闲连接 单位毫秒
  redis:  # redis 配置
    host: localhost
    port: 6379
#    password:  # redis密码，没有注释掉

# mybatis-plus 配置
mybatis-plus:
  mapper-locations: classpath*:/mapper/**/*.xml # 默认值，扫描mapper.xml文件
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # 日志输出
  global-config:
    db-config:
      logic-delete-field: isDelete # 逻辑删除 需要加一个注解 @TableLogic
      logic-not-delete-value: 0 # 未删除逻辑值
      logic-delete-value: 1 # 已经删除逻辑值

# Dubbo服务提供者
#dubbo:
#  application:
#    name: provide
#  registry:
#    address: zookeeper://localhost:2181
#  scan:
#    base-packages: com.dyf # 这里配置Dubbo的注解所在包,扫描的是接口实现类


# Dubbo消费者
#dubbo:
#  application:
#    name: cosumer
#  registry:
#    address: zookeeper://localhost:2181
#  scan:
#    base-packages: com.dyf # 这里配置Dubbo的注解所在包,扫描的是controller5. SpringBoot整合MybatisPlus分页插件配置类@Configuration
public class MyBatisPlusConfig {
    /**
     * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 这里需要指定数据库类型 这里使用的Mysql
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}6. 整合所需要的 jar 包导入SpringBoot 父类项目    &lt;parent&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;
        &lt;version&gt;2.7.11&lt;/version&gt;
        &lt;relativePath/&gt; &lt;!-- lookup parent from repository --&gt;
    &lt;/parent&gt;导入所需要的 Jar 包    &lt;dependencies&gt;
        &lt;!--redis--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-data-redis&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;!--springboot Web包--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
        &lt;/dependency&gt;
        &lt;!--springBoot 测试包--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt;
            &lt;scope&gt;test&lt;/scope&gt;
        &lt;/dependency&gt;
        &lt;!--Druid 数据库连接池包--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;com.alibaba&lt;/groupId&gt;
            &lt;artifactId&gt;druid-spring-boot-starter&lt;/artifactId&gt;
            &lt;version&gt;1.2.16&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;!--mybatisPlus包--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;com.baomidou&lt;/groupId&gt;
            &lt;artifactId&gt;mybatis-plus-boot-starter&lt;/artifactId&gt;
            &lt;version&gt;3.5.3.1&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;!--redis Jar包--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-starter-data-redis&lt;/artifactId&gt;
        &lt;/dependency&gt;

        &lt;!--Dubbo Jar包--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.apache.dubbo&lt;/groupId&gt;
            &lt;artifactId&gt;dubbo-spring-boot-starter&lt;/artifactId&gt;
            &lt;version&gt;2.7.8&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;!--zookeep包，注册中心，整合Dubbo时候使用--&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.apache.dubbo&lt;/groupId&gt;
            &lt;artifactId&gt;dubbo-dependencies-zookeeper&lt;/artifactId&gt;
            &lt;version&gt;2.7.8&lt;/version&gt;
            &lt;type&gt;pom&lt;/type&gt;
            &lt;!--取消引入这两个日志框架，若引入控制台会报错，Jar重复引入--&gt;
            &lt;exclusions&gt;
                &lt;exclusion&gt;
                    &lt;groupId&gt;org.slf4j&lt;/groupId&gt;
                    &lt;artifactId&gt;slf4j-reload4j&lt;/artifactId&gt;
                &lt;/exclusion&gt;
                &lt;exclusion&gt;
                    &lt;groupId&gt;log4j&lt;/groupId&gt;
                    &lt;artifactId&gt;log4j&lt;/artifactId&gt;
                &lt;/exclusion&gt;
            &lt;/exclusions&gt;
        &lt;/dependency&gt;

    &lt;/dependencies&gt;

    &lt;build&gt;
        &lt;plugins&gt;
            &lt;plugin&gt;
                &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
                &lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;
                &lt;configuration&gt;
                    &lt;excludes&gt;
                        &lt;exclude&gt;
                            &lt;groupId&gt;org.projectlombok&lt;/groupId&gt;
                            &lt;artifactId&gt;lombok&lt;/artifactId&gt;
                        &lt;/exclude&gt;
                    &lt;/excludes&gt;
                &lt;/configuration&gt;
            &lt;/plugin&gt;
        &lt;/plugins&gt;
    &lt;/build&gt;</description>
</item>
<item rdf:about="http://blog.anlucky.cn/index.php/programming/java/136">
<title>Java工具类-JWT工具类</title>
<link>http://blog.anlucky.cn/index.php/programming/java/136</link>
<dc:date>2023-05-12T13:26:01+08:00</dc:date>
<description>使用方式1. 生成 JWT令牌        // 配置要存储的数据
        HashMap&lt;String, Object&gt; map = new HashMap&lt;&gt;();
        map.put(&quot;id&quot;,1);
        map.put(&quot;name&quot;,&quot;LuckyDu&quot;);

        // 使用默认配置生成JWT令牌
        String jwtToken = JWTUtils.getJwtToken(map);
        System.out.println(&quot;jwtToken = &quot; + jwtToken);

        // 自定义配置生成JWT令牌
        SignatureAlgorithm hs256 = SignatureAlgorithm.HS256;// 设置签名算法
        String signKey = &quot;luckyDu&quot;; // 加盐 不能过短，过短会报错,至少4个字符，具体版本不同可能不一样
        Date outTime = new Date(System.currentTimeMillis() + 3600 * 1000);
        String jwtToken1 = JWTUtils.getJwtToken(hs256, signKey, map, outTime);
        System.out.println(&quot;jwtToken1 = &quot; + jwtToken1);2. 解析 JWT令牌        // 生成的JWT令牌
        String jwtToken = &quot;【这里填写您生成的jwt令牌】&quot;;
        // 使用默认配置解析
        Jws&lt;Claims&gt; claimsJws = JWTUtils.parseJwtToken(jwtToken);
        System.out.println(&quot;claimsJws = &quot; + claimsJws);

        // 使用自定义配置解析
        String signKey = &quot;luckyDu&quot;; // 自定义的生成jwt令牌的 盐值
        Jws&lt;Claims&gt; claimsJws1 = JWTUtils.parseJwtToken(jwtToken, signKey);
        System.out.println(&quot;claimsJws1 = &quot; + claimsJws1);3. JWT工具类import io.jsonwebtoken.*;

import java.io.Serializable;
import java.util.Date;
import java.util.Map;

/**
 * JWT 生成工具类
 */
public class JWTUtils implements Serializable {
    /**
     * 设置盐值
     */
    private static final String signKey = &quot;LuckyDu&quot;;

    /**
     * 设置Token过期时间 一小时
     */
    private static final Date outTime = new Date(System.currentTimeMillis() + 3600 * 1000);

    /**
     * 设置加密算法 HS256
     */
    private static final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;


    /**
     * 生成 JWT Token
     * @param signatureAlgorithm 签名算法
     * @param signKey 自定义盐值
     * @param map 数据
     * @param outTime 过期时间
     * @return
     */
    public static String getJwtToken(SignatureAlgorithm signatureAlgorithm,String signKey,Map&lt;String,Object&gt; map,Date outTime){
        String compact = Jwts.builder()
                .signWith(signatureAlgorithm, signKey) // 设置JWT头部信息，加密算法和盐值
                .setClaims(map) // 设置JWT的荷载信息，需要一个Map集合存储
                .setExpiration(outTime) // 设置过期时间，设置的是时间点
                .compact(); // 调用生成令牌
        return compact;
    }

    /**
     * 使用默认配置生成JWT token
     * @param map 数据 Map
     * @return
     */
    public static String getJwtToken(Map&lt;String,Object&gt; map){
        String jwtToken = getJwtToken(SIGNATURE_ALGORITHM, signKey, map, outTime);
        return jwtToken;
    }

    /**
     * 解析 JWT token
     * @param compact 令牌字符串
     * @param signKey 生成时候的盐值
     * @return
     * @throws ExpiredJwtException JWT令牌过期抛出异常
     * @throws UnsupportedJwtException jwt令牌不支持的异常
     * @throws MalformedJwtException 头部信息出错抛出异常
     * @throws SignatureException 荷载信息出错抛出异常 签名信息出错抛出异常
     * @throws IllegalArgumentException 传入参数异常
     */
    public static Jws&lt;Claims&gt; parseJwtToken(String compact ,String signKey) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException{
        Jws&lt;Claims&gt; claimsJws = Jwts.parser() // 解析JWT令牌方法
                .setSigningKey(signKey) // 设置盐值
                .parseClaimsJws(compact);// 传入生成的令牌
        return claimsJws;
    }

    /**
     *
     * @param compact 令牌字符串
     * @return
     * @throws ExpiredJwtException
     * @throws UnsupportedJwtException
     * @throws MalformedJwtException
     * @throws SignatureException
     * @throws IllegalArgumentException
     */
    public static Jws&lt;Claims&gt; parseJwtToken(String compact) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException{
        Jws&lt;Claims&gt; claimsJws = parseJwtToken(compact,signKey);// 传入生成的令牌
        return claimsJws;
    }

}</description>
</item>
<item rdf:about="http://blog.anlucky.cn/index.php/programming/java/133">
<title>IDEA中如何将方法的注释一并重写</title>
<link>http://blog.anlucky.cn/index.php/programming/java/133</link>
<dc:date>2023-05-11T21:37:54+08:00</dc:date>
<description>1. 需求分析在我们设计接口的时候，通常情况下会将注释一并加上去，但是我们的接口中的注释往往不会跟着我们的接口实现而重写过来，每次都需要拷贝，非常麻烦，接下来就是一个如何设置IDEA将注释也一并重写过来2. 设置方式1. 现有接口2. 在实现类中重写只需要勾选重写窗口中的 Copy JavaDoc 即可注意：​        单行注释和多行注释都不支持被重写，只支持文档注释，下面会介绍Java中的注释3. Java中的注释1. 普通注释普通注释的注释方法 //如：普通注释只可以注释单行内容2. 多行注释多行注释可以注释多行内容，需要在注释的范围内，不可以超越范围3. 文档注释文档注释也可以多行，文档注释加在方法上面可以生成一些方法上面的内容，如参数 和返回值，并且支持被重写复制到实现类上如何快捷生成：在IDEA中（其他工具方法可能不同），在方法上面输入 /** 然后按一下回车就可以生成了</description>
</item>
<item rdf:about="http://blog.anlucky.cn/index.php/programming/java/124">
<title>Java工具类-发送邮件(支持HTM)</title>
<link>http://blog.anlucky.cn/index.php/programming/java/124</link>
<dc:date>2023-04-28T16:17:28+08:00</dc:date>
<description>Java工具类-发送邮件支持HTM1. 导入Maven坐标  &lt;dependency&gt;
        &lt;groupId&gt;javax.mail&lt;/groupId&gt;
        &lt;artifactId&gt;mail&lt;/artifactId&gt;
        &lt;version&gt;1.4.7&lt;/version&gt;
    &lt;/dependency&gt;2. 创建Java发送邮件工具类/**
 * 发送邮件工具类
 */
public class SendEmailUtils {
    
    private String user; // 发件人称号，同邮箱地址
    private String password; // 如果是qq邮箱可以使户端授权码，或者登录密码
    private String host;    // 指定发送邮件的主机QQ为 smtp.qq.com

    public String getUser() {
        return user;
    }

    public void setUser(String user) {
        this.user = user;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    /**
     * 创建邮件发送的对象
     *
     * @param USER     发件人称号，同邮箱地址
     * @param PASSWORD 如果是qq邮箱可以使户端授权码，或者登录密码
     * @param HOST     指定发送邮件的主机QQ为 smtp.qq.com
     */
    public SendEmailUtils(String USER, String PASSWORD, String HOST) {
        this.user = USER;
        this.password = PASSWORD;
        this.host = HOST;
    }

    /**
     * 发送邮件 支持HTML标签
     * @param toEmail 邮件接收方的邮箱
     * @param headText 邮件的头部主题信息
     * @param bodyText 邮件的主体部分实质内容
     * @return 成功返回true 失败返回false
     */
    public boolean send(String toEmail, String headText, String bodyText) {
        // 获取系统属性
        Properties properties = System.getProperties();
        // 设置邮件服务器
        properties.setProperty(&quot;mail.smtp.host&quot;, getHost());
        properties.put(&quot;mail.smtp.auth&quot;, &quot;true&quot;);
        // 获取默认session对象
        Session session = Session.getDefaultInstance(properties, new Authenticator() {
            public PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication(getUser(), getPassword()); //发件人邮件用户名、授权码
            }
        });
        boolean success = true;
        try {
            // 创建默认的 MimeMessage 对象
            MimeMessage message = new MimeMessage(session);

            // Set From: 头部头字段
            message.setFrom(new InternetAddress(user));

            // Set To: 头部头字段
            message.addRecipient(Message.RecipientType.TO,
                    new InternetAddress(toEmail));

            // Set Subject: 头部头字段
            message.setSubject(headText);

            // 设置邮件的内容体
            message.setContent(bodyText, &quot;text/html;charset=UTF-8&quot;);

            // 发送消息
            Transport.send(message);

        } catch (MessagingException mex) {
            success =false;
            mex.printStackTrace();
        }

        return success;

    }
}3. 使用Java工具类QQ邮箱授权码需要在QQ邮箱中获取别的邮箱的授权码可以百度搜索在哪里获取，一般情况下QQ使用的比较多    public static void main(String[] args) {
        String user = &quot;xxx@qq.com&quot;; // 发件人称号，同邮箱地址
        String password = &quot;邮箱授权码&quot;; // 如果是qq邮箱可以使户端授权码，或者登录密码
        String host = &quot;smtp.qq.com&quot;;    // 指定发送邮件的主机QQ为 smtp.qq.com
        SendEmailUtils sendEmailUtils = new SendEmailUtils(user,password,host);
        String toEmail=&quot;xxx@qq.com&quot;;
        String head=&quot;头信息&quot;;
        String body=&quot;&lt;h1&gt;主题信息&lt;/h1&gt;&quot;;
        boolean send = sendEmailUtils.send(toEmail, head, body);
        System.out.println(&quot;send = &quot; + send);
    }</description>
</item>
<item rdf:about="http://blog.anlucky.cn/index.php/programming/java/123">
<title>Java工具类-图片验证码</title>
<link>http://blog.anlucky.cn/index.php/programming/java/123</link>
<dc:date>2023-04-28T14:28:00+08:00</dc:date>
<description>1. 创建接收实体类这个实体类是可以更变的，具体的实体类可以根据自己的业务需求去编写，这里使用的是一个比较通用一点的实体类来接收，也可以不使用实体类接收，直接使用，不过在日常开发中，不建议这样去做创建实体，并生成他的 Get 和 Set 方法，有参数构造和无参数构造public class VerifyCode {
    private String code;   // 验证码文字信息
    private BufferedImage imgs; // 图片验证码信息
}2. 验证码工具类import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;

/**
 * 获取验证码
 */
public class VerifyCodeUtils {

    private int height = 30;  // 图片宽

    private int width = 80;   // 图片高

    private int codeNumber = 4; // 生成的验证码个数

    // 定义有哪些字符验证码
    private String codeString = &quot;23456789abcdefghjkmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ&quot;;
    // 定义背景颜色
    private Color backgroundColor = new Color(255, 255, 255);

    // 定义字体
    private String[] fontNames = { &quot;宋体&quot;, &quot;华文楷体&quot;, &quot;黑体&quot;, &quot;微软雅黑&quot;, &quot;楷体_GB2312&quot;};

    // 获取一个验证码，用来生成图片
    public String getCodeText(){
        StringBuffer codeText = new StringBuffer();
        for (int i = 0; i &lt; codeNumber; i++) {
            int randomNumber = getRandomNumber(1,codeString.length());
            codeText.append(codeString.charAt(randomNumber));
        }
        return  codeText.toString();
    }
    // 获取一个随机数字，用来取得codeString中的随机一个字母
    public int getRandomNumber(int start,int end){
        int randomNumber = (int) (Math.random() * (end-start)+start);
        return randomNumber;
    }

    // 生成一个随机颜色
    public Color getRandomColor(){
        int r = getRandomNumber(1,255);
        int g = getRandomNumber(1,255);
        int b = getRandomNumber(1,255);
        Color color = new Color(r,g,b);
        return color;
    }
    // 生成一个随机字体
    public Font getRandomFont(){
        // 字体 如黑体
        String fontStyle = fontNames[getRandomNumber(0,fontNames.length)];
        //  字体加粗斜体 Font.BOLD = 1     Font.ITALIC = 2     Font.BOLD + Font.ITALIC = 3
        int fontBlod = getRandomNumber(1,4);
        // 定义字体大小
        int size = getRandomNumber(24,30);
//        System.out.println(&quot;字体：&quot; + fontStyle + &quot;   加粗：&quot; + fontBlod + &quot;   字体大小：&quot; + size);
        Font font = new Font(fontStyle,fontBlod,size);
        return font;
    }

    // 获取图片验证码
    public VerifyCode getCodeImge(){
        // 创建一个验证码的实体
        VerifyCode verifyCode = new VerifyCode();
        // 创建画板  不带透明色的画板
        BufferedImage bufferedImage = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
        // 获取此画板的画笔
        Graphics graphics = bufferedImage.getGraphics();
        // 设置画笔的颜色
        graphics.setColor(backgroundColor);
        // 填充画板背景颜色
        graphics.fillRect(0,0,width,height);
        // 获取随机验证码文字
        String codeText = getCodeText();
        // 在画板上画出验证码
        for (int i = 0; i &lt; codeNumber; i++) {
            // 给画笔设置一个随机颜色
            graphics.setColor(getRandomColor());
            // 设置一个随机颜色
            graphics.setFont(getRandomFont());
            // 设置验证码的X坐标 当前是第几个字符转为float 乘以画板大小，除以验证码个数等于平均每个验证码的位置
            float x = i*1.0f*width/codeNumber;
            int y = height - getRandomNumber(8,height-(8*2));
            // 将验证码逐个写到图片上
            graphics.drawString(codeText.charAt(i)+&quot;&quot;, (int) x,y);
        }
        // 在实体中添加验证码的信息
        verifyCode.setCode(codeText);
        verifyCode.setImgs(bufferedImage);
        return verifyCode;
    }
}
3. 使用验证码工具类这个代码是JavaServlet中的一个请求，是最基础的web请求和响应方式，在使用中可以根据自己的实际使用场景去使用，这里只做了一个大概的演示/**
 * 获取验证码
 */
// 注解servlet注册
@WebServlet(&quot;/getVerify&quot;)
public class VerifyCodeServlet extends ServletUtils {
    // 这里的servlet Utils和继承HttpServlet中的doGet和doPost方法一致
    public void getVerify(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 创建工具类对象
        VerifyCodeUtils verifyCodeUtils = new VerifyCodeUtils();
        // 获取验证码，返回实体对象
        VerifyCode codeImge = verifyCodeUtils.getCodeImge();
        // 查询验证码是什么
        System.out.println(&quot;验证码：&quot; + codeImge.getCode());
        // 在session域中放入验证码
        req.getSession().setAttribute(&quot;registerVerifyCode&quot;,codeImge.getCode());
        // 将图片写到页面上
        ImageIO.write(codeImge.getImgs(),&quot;png&quot;,resp.getOutputStream());
    }
}</description>
</item>
</rdf:RDF>