<?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="https://blog.anlucky.cn/index.php/feed/rss/tag/Java-Web/">
<title>LuckyDu - Java Web</title>
<link>https://blog.anlucky.cn/index.php/tag/Java-Web/</link>
<description></description>
<items>
<rdf:Seq>
<rdf:li resource="https://blog.anlucky.cn/index.php/programming/java/215"/>
<rdf:li resource="https://blog.anlucky.cn/index.php/programming/java/213"/>
<rdf:li resource="https://blog.anlucky.cn/index.php/programming/database/152"/>
<rdf:li resource="https://blog.anlucky.cn/index.php/programming/java/148"/>
<rdf:li resource="https://blog.anlucky.cn/index.php/programming/java/141"/>
<rdf:li resource="https://blog.anlucky.cn/index.php/programming/java/124"/>
<rdf:li resource="https://blog.anlucky.cn/index.php/programming/java/123"/>
<rdf:li resource="https://blog.anlucky.cn/index.php/programming/java/122"/>
</rdf:Seq>
</items>
</channel>
<item rdf:about="https://blog.anlucky.cn/index.php/programming/java/215">
<title>SpringBoot解决全局跨域问题</title>
<link>https://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="https://blog.anlucky.cn/index.php/programming/java/213">
<title>Java工具类-通用对象转换为Json字符</title>
<link>https://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="https://blog.anlucky.cn/index.php/programming/database/152">
<title>缓存穿透，缓存击穿，缓存雪崩是什么如何解决</title>
<link>https://blog.anlucky.cn/index.php/programming/database/152</link>
<dc:date>2023-05-23T20:26:11+08:00</dc:date>
<description>1. 什么是缓存穿透缓存穿透指的是一个缓存系统无法缓存某个查询的数据，从而导致这个查询每一次都要访问数据库个人理解，缓存穿透是指要访问的数据既不在缓存中，也不在数据库中，导致请求在访问缓存时没有找到对应的数据，再去请求数据库也没有找到数据，无法将数据写入缓存，这样一来缓存就变为了摆设，就会同时给缓存和数据库造成压力常见的Redis缓存穿透场景包括：查询一个不存在的数据，攻击者可能发送一些无效的查询来触发缓存穿透查询一些非常热门的数据，如果一个数据被访问的非常频繁，那么可能会导致缓存系统无法处理这些请求，从而造成缓存穿透查询一些异常数据：这种情况通常发生在数据服务出现故障或异常时，从而造成缓存系统无法访问相关数据，从而导致缓存穿透。2. 如何解决缓存穿透解决方案一缓存空值或者缺省的值一旦发生缓存穿透，我们就可以针对查询的数据，再Redis中缓存一个空值或者确定的缺省值，（就是随便一个可以表示缺省的值），紧接着，发送后续的查询的时候就可以从缓存中获取到缺省值了，保证了数据库的正常运行解决方案二使用布隆过滤器快速判断数据是否存在3. 什么是缓存击穿缓存击穿往往出现在高并发访问的情况下，一个热点数据从缓存在查不存在，就要去数据库中查询，从而导致数据库的压力过大，导致系统性能下降缓存击穿的原因通常有以下几种：缓存中不存在所需要的热点数据，每当系统中的某个热点数据需要频繁访问的时候，同时去访问数据库，给数据库造负担缓存热点的数据过期，当一个热点数据过期，并且需要重新缓存时候，若此时有大量请求也会因为缓存中的数据过期而去数据库中查询，给数据库造成负担。4. 如何解决缓存击穿解决方案一设置热点数据永不过期设置热点数据永不过期就是不给key设置过期的时间即可还有第二种方式也可以达到key值永不过期，就是正常的给key值设置过期时间，在后台启动一个定时任务在他过期前去定时地更新这个缓存，并重新设置他的过期时间解决方案二分布式锁并发更新锁的对象就是key，这样如果有大量请求并发进来时，只能有一个请求获取到锁，然后获取到锁的线程去查询数据库，将查询到的数据放入到缓存中，此时，缓存中已经有数据了，后面的请求就可以从缓存中获取到数据返回，并不会查询数据库5. 什么是缓存雪崩缓存雪崩是指大量的请求无法在Redis中进行处理，紧接着大量的请求发送到数据库层，导致数据库压力过大常见的缓存雪崩的场景：缓存服务器宕机，当缓存服务器宕机或者重启时，大量的访问请求直接命中数据库，在同一时间导致大量的数据查询，从而数据库的压力增大缓存数据同时失效，在某个特定的时间点，缓存中的数据大量失效，并在同一个时间点，有大量的请求集中在一起6. 如何解决缓存雪崩解决方案一避免给大量的数据设置相同的过期时间，如果业务层有些数据同时失效，可以给每个数据设置一个较短的过期间隔，(在不影响业务的情况下，特殊情况特殊处理)解决方案二缓存预热缓存预热就是在系统启动或者失效时，手动加载数据到缓存中，这样实际请求的时候就可以直接从缓存中获取数据</description>
</item>
<item rdf:about="https://blog.anlucky.cn/index.php/programming/java/148">
<title>SpringSecurity安全框架基础入门（前后端分离）</title>
<link>https://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="https://blog.anlucky.cn/index.php/programming/java/141">
<title>SpringBoot基础入门</title>
<link>https://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="https://blog.anlucky.cn/index.php/programming/java/124">
<title>Java工具类-发送邮件(支持HTM)</title>
<link>https://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="https://blog.anlucky.cn/index.php/programming/java/123">
<title>Java工具类-图片验证码</title>
<link>https://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>
<item rdf:about="https://blog.anlucky.cn/index.php/programming/java/122">
<title> Freemarker 基础入门</title>
<link>https://blog.anlucky.cn/index.php/programming/java/122</link>
<dc:date>2023-04-27T14:52:28+08:00</dc:date>
<description>官网：FreeMarker Java Template Engine (apache.org)FreeMarker 是一个用 Java 语言编写的模板引擎，它基于模板来生成文本输出。FreeMarker与 Web 容 器无关，即在 Web 运行时，它并不知道 Servlet 或 HTTP。它不仅可以用作表现层的实现技术，而且还 可以用于生成 XML，JSP 或 Java 等。Freemarker 模板语言是FTL文件编写的（Freemarker 文件后缀 .ftl）虽然FreeMarker最初是为生成HTML页面而创建的 MVC Web 应用程序框架，它不绑定到 servlet 或 HTML 或 任何与网络相关的内容。它在非 Web 应用程序环境中运作。1. FreeMarker的几个亮点强大的模板语言：条件块，迭代，赋值，字符串和算术运算和格式设置，宏和函数，包括其他模板，默认情况下可以转义(可选)，等等多用途和轻量级：零依赖，任何输出格式，可以从任何地方加载模板，可插拔许多配置选项国际化：区域设置敏感，数字和日期/时间格式，本地化模板变化XML处理功能：将XML Dom-S放入数据建模和遍历，甚至处理他们，使用声明方式通用数据模型：Java对象向模板公开，作为可插拔适配器的变量树，他决定模板如何看到他们2. 从官网中获取当前最新的稳定版本号官网：下载 / Maven - Apache FreeMarker™获取到Maven坐标&lt;dependency&gt;
  &lt;groupId&gt;org.freemarker&lt;/groupId&gt;
  &lt;artifactId&gt;freemarker&lt;/artifactId&gt;
  &lt;version&gt;2.3.32&lt;/version&gt;
&lt;/dependency&gt;直到2007年左右，Maven组名是 “freemarker”而不是“org.freemarker”， 正如上面的XML注释所说，这可能会导致问题，因为Maven 将将它们视为两个独立的工件，没有版本冲突。 如果您遇到此问题，请找到依赖于旧 FreeMarker 的，并插入其中。&lt;exclusions&gt;
    &lt;exclusion&gt;
        &lt;groupId&gt;freemarker&lt;!-- Legacy org-less group --&gt;&lt;/groupId&gt;
        &lt;artifactId&gt;freemarker&lt;/artifactId&gt; 
    &lt;/exclusion&gt;
&lt;/exclusions&gt;`还有一个单独的Google App Engine兼容 （“gae”）变体&lt;dependency&gt;
  &lt;groupId&gt;org.freemarker&lt;/groupId&gt;
  &lt;artifactId&gt;freemarker-gae&lt;/artifactId&gt;
  &lt;version&gt;2.3.32&lt;/version&gt;
&lt;/dependency&gt;3. 基础使用范例文件准备创建一个文件夹，在文件夹中创建一个以 .ftl 结尾的文件，这里使用的是一个index.ftl文件&lt;html&gt;
    &lt;head&gt;
        &lt;meta charset=&quot;utf-8&quot;&gt;
        &lt;title&gt;Freemarker入门&lt;/title&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;#--我只是一个注释，我不会有任何输出 --&gt;
        ${name}你好，${message}
    &lt;/body&gt;
&lt;/html&gt;创建一个FreemarkerTest.class 类文件        // 创建Freemarker中的configuration对象，构造方法里面传入版本号
        Configuration configuration = new Configuration(Configuration.getVersion());
        // 设置模板所在的目录
        configuration.setDirectoryForTemplateLoading(new File(&quot;C:\\Users\\DYF\\Desktop\\ftl&quot;));
        // 设置编码集
        configuration.setDefaultEncoding(&quot;utf-8&quot;);
        // 加载模板，加载对应的模板文件
        Template template = configuration.getTemplate(&quot;index.ftl&quot;);
        // 创建数据模型
        HashMap&lt;String, String&gt; map = new HashMap&lt;&gt;();
        map.put(&quot;name&quot;,&quot;张三&quot;);
        map.put(&quot;message&quot;,&quot;这条消息是message&quot;);
        // 创建输出流
        FileWriter fileWriter = new FileWriter(new File(&quot;C:\\Users\\DYF\\Desktop\\ftl\\index.html&quot;));
        // 生成对应的Html静态文件
        template.process(map,fileWriter);
        // 关闭流
        fileWriter.close();运行代码，会发现当前设置的目录下多了一个index.html 文件，打开之后，发现生成了一个静态文件，直接将 ${} 所包含的文本直接生成了一个静态的数据在项目中应用时可以将Configuration对象的创建交由Spring框架来完成，并通过依赖注入方 式将字符集和模板所在目录注入进去。1. 数据设置原理比如有模板文件&lt;html&gt;
&lt;head&gt;
  &lt;title&gt;Welcome!&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;h1&gt;Welcome ${user.name}!&lt;/h1&gt;
  &lt;p&gt;Our latest product:
  &lt;a href=&quot;${user.latestProduct.name}&quot;&gt;${user.latestProduct.name}&lt;/a&gt;&lt;/br&gt;
  &lt;a href=&quot;${user.latestProduct.arrTest[0]}&quot;&gt;${user.latestProduct.arrTest[0]}&lt;/a&gt;
&lt;/body&gt;
&lt;/html&gt;有Java对象User 并且生成Get和Set方法public class User {
    private String name;
    private LatestProduct latestProduct;
}有Java对象 LatestProduct 并且生成Get和Set方法public class LatestProduct {
    private String name;
    private Integer[] arrTest;
}测试代码    public static void main(String[] args) throws Exception {
        // 创建Freemarker中的configuration对象，构造方法里面传入版本号
        Configuration configuration = new Configuration(Configuration.getVersion());
        // 设置模板所在的目录
        configuration.setDirectoryForTemplateLoading(new File(&quot;C:\\Users\\DYF\\Desktop\\ftl&quot;));
        // 设置编码集
        configuration.setDefaultEncoding(&quot;utf-8&quot;);
        // 加载模板
        Template template = configuration.getTemplate(&quot;index2.ftl&quot;);
        // 创建数据模型
        HashMap&lt;String, Object&gt; map = new HashMap&lt;&gt;();
        Integer[] arr = {1,2,3,4};
        User user = new User(&quot;李四&quot;, new LatestProduct(&quot;王五&quot;, arr));
        map.put(&quot;user&quot;,user);
        // 创建输出流
        FileWriter fileWriter = new FileWriter(new File(&quot;C:\\Users\\DYF\\Desktop\\ftl\\index2.html&quot;));
        template.process(map,fileWriter);
        fileWriter.close();
    }我们会发现在Freemarker中模板注入使用的方式和jsp中的表达式差不多一致2. 使用注意不需要重复创建 `Configuration` 实例； 它的代价很高，尤其是会丢失缓存。`Configuration` 实例就是应用级别的单例。数据模型+模板=输出数据模型：就是Java对象，使用Map集合将Java对象放入，然后页面中可以调用模板，及页面中的模板取值4. FreeMarker的常用指令1. assign 指令assign指令用于在页面上定义一个变量定义简单数据类型    &lt;#assign linkman=&quot;周先生&quot;&gt;
    联系人：${linkman}
    &lt;#assign 变量名=值 变量名=值&gt; （定义多个变量）
    &lt;#assign num=1 names=[&quot;zhangsan&quot;,&quot;lisi&quot;,&quot;wangwu&quot;] &gt; (定义数组，遍历数组)
    ${num} -- ${names?join(&quot;,&quot;)}定义对象类型&lt;#assign info={&quot;mobile&quot;:&quot;1391XXXXXX&quot;,&#039;address&#039;:&#039;郑州市金水区&#039;} &gt;
电话：${info.mobile} 地址：${info.address}2.  include指令include指令用于模板的文件的嵌套使用步骤：创建一个模板文件如：head.ftl&lt;h1&gt;程序员&lt;/h1&gt;创建另一个模板文件如：test.ftl&lt;#include &quot;head.ftl&quot;/&gt;包含多种文件&lt;#--包含指令(引入其他页面文件) include--&gt;
&lt;#--html文件--&gt;
&lt;#include &quot;test.html&quot;&gt; 
&lt;#--freemarker文件--&gt;
&lt;#include &quot;test.ftl&quot;&gt; 
&lt;#--text文件--&gt;
&lt;#include &quot;test.txt&quot;&gt;注意：被包含的文件和包含它的模板共享变量，就像是被复制粘贴进去的一样。3.  if 指令​    if 指令用于条件判断，选择判断使用&lt;#if success=true&gt;
你已通过实名认证
&lt;#else&gt;
你未通过实名认证
&lt;/#if&gt;在模板中加入下面代码map.put(&quot;success&quot;,true);在freemarker的判断中，可以使用= 也可以使用==4. if else if 指令if else if 使用多条判断语句&lt;#if 条件&gt;
 ...
&lt;#elseif 条件&gt;
 ...
&lt;#elseif 条件&gt;
 ...
&lt;#else&gt;5. list指令list集合用于遍历创建模板文件&lt;#list goodsList as goods&gt;
商品名称： ${goods.name} 价格：${goods.price}&lt;br&gt;
&lt;/#list&gt;创建Java类代码List goodsList=new ArrayList();
Map goods1=new HashMap();
goods1.put(&quot;name&quot;, &quot;苹果&quot;);
goods1.put(&quot;price&quot;, 5.8);
Map goods2=new HashMap();
goods2.put(&quot;name&quot;, &quot;香蕉&quot;);
goods2.put(&quot;price&quot;, 2.5);
Map goods3=new HashMap();
goods3.put(&quot;name&quot;, &quot;橘子&quot;);
goods3.put(&quot;price&quot;, 3.2);
goodsList.add(goods1);
goodsList.add(goods2);
goodsList.add(goods3);
map.put(&quot;goodsList&quot;, goodsList);5. macro 自定义指令1. 指令可以被多次使用。
2. 自定义指令中可以包含字符串，也可包含内置指令1. 自定义指令声明指令  &lt;#macro 指令名&gt;
     指令内容
  &lt;/#macro&gt;使用指令&lt;@指令名&gt;&lt;/@指令名&gt;使用方式&lt;html&gt;
&lt;head&gt;
&lt;meta charset=&quot;utf-8&quot;&gt;
&lt;title&gt;Freemarker入门&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;#macro test&gt; 这个测试指令&lt;/#macro&gt;
使用指令
&lt;@test&gt;&lt;/@test&gt;
&lt;/body&gt;
&lt;/html&gt;java代码可以直接写上面的测试使用范例，编译执行即可2. 自定义指令(带参数)  声明格式：
       &lt;#macro 指令名 参数名1 参数名2&gt;
          指令内容
      &lt;/#macro&gt;
  使用格式：
      &lt;@指令名 参数名1=参数值1 参数名2=参数值2&gt;&lt;/@指令名&gt;使用方式&lt;html&gt;
&lt;head&gt;
&lt;meta charset=&quot;utf-8&quot;&gt;
&lt;title&gt;Freemarker入门&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    定义指令
&lt;#macro test a b&gt;${a}&lt;/br&gt;${b}&lt;/#macro&gt;
使用指令
&lt;@test a=1 b=2&gt;&lt;/@test&gt;
&lt;/body&gt;
&lt;/html&gt;3. 自定义指令(包含内置指令)使用方式&lt;html&gt;
&lt;head&gt;
&lt;meta charset=&quot;utf-8&quot;&gt;
&lt;title&gt;Freemarker入门&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;#macro test&gt;
遍历1到9 使用 i 给每次遍历的结果赋值&lt;/br&gt;
 &lt;#list 1..9 as i&gt;
    遍历1到 i 使用 j 给每次遍历的结果赋值
        &lt;#list 1..i as j&gt;
           ${j}*${i}=${j*i}&amp;nbsp;
        &lt;/#list&gt;
        &lt;br&gt;
    &lt;/#list&gt; 
&lt;/#macro&gt;
使用指令&lt;/br&gt;
&lt;@test&gt;&lt;/@test&gt;
&lt;/body&gt;
&lt;/html&gt;6. nested 占位符指令nested占位符一般会和macro一起使用使用方式&lt;html&gt;
&lt;head&gt;
&lt;meta charset=&quot;utf-8&quot;&gt;
&lt;title&gt;Freemarker入门&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;#macro test&gt;
    这是一段文本！
    &lt;#nested&gt;
    &lt;#nested&gt; 
&lt;/#macro&gt;
使用指令&lt;/br&gt;
&lt;@test&gt;&lt;h4&gt;这是文本后面的内容！&lt;/h4&gt;&lt;/@test&gt;
&lt;/body&gt;
&lt;/html&gt;7、import 导入指令import 指令可以引入一个库。也就是说，它创建一个新的命名空间， 然后在那个命名空间中执行给定路径的模板。可以使用引入的空间中的指令。创建commons.ftl文件&lt;#macro cfb&gt;
    &lt;#list 1..9 as i&gt;
        &lt;#list 1..i as j&gt;
           ${j}*${i}=${j*i}&amp;nbsp;
        &lt;/#list&gt;
        &lt;br&gt;
    &lt;/#list&gt; 
&lt;/#macro&gt;在其他ftl页面中通过import导入commons.ftl的命名空间，使用该命名空间中的指令创建test.ftl文件&lt;#-- 导入命名空间 --&gt;
&lt;#import &quot;commons.ftl&quot; as common&gt; 
&lt;#-- 使用命名空间中的指令 --&gt;
&lt;@common.cfb&gt;&lt;/@common.cfb&gt;</description>
</item>
</rdf:RDF>