<?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/%E7%BC%96%E7%A8%8B%E6%8A%80%E6%9C%AF/">
<title>LuckyDu - 编程技术</title>
<link>https://blog.anlucky.cn/index.php/tag/%E7%BC%96%E7%A8%8B%E6%8A%80%E6%9C%AF/</link>
<description></description>
<items>
<rdf:Seq>
<rdf:li resource="https://blog.anlucky.cn/index.php/programming/java/213"/>
<rdf:li resource="https://blog.anlucky.cn/index.php/programming/tools/153"/>
<rdf:li resource="https://blog.anlucky.cn/index.php/programming/java/148"/>
<rdf:li resource="https://blog.anlucky.cn/index.php/programming/database/147"/>
<rdf:li resource="https://blog.anlucky.cn/index.php/programming/java/141"/>
<rdf:li resource="https://blog.anlucky.cn/index.php/programming/java/133"/>
<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:li resource="https://blog.anlucky.cn/index.php/programming/116"/>
</rdf:Seq>
</items>
</channel>
<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/tools/153">
<title>Docker的基本命令</title>
<link>https://blog.anlucky.cn/index.php/programming/tools/153</link>
<dc:date>2023-05-27T09:15:00+08:00</dc:date>
<description>Docker的基本命令1. 帮助启动相关命令功能命令行代码启动dockersystemctl start docker停止dockersystemctl stop docker重启dockersystemctl restart docker查看docker状态systemctl status docker开机启动dockersystemctl enable docker查看docker概要信息docker info查看docker总体帮助文档docker --help查看docker命令帮助文档docker save --help (查看save命令的帮助文档)2. 镜像相关命令1. docker images 查看本地的镜像其中repository 表示镜像名称Tag  表示镜像的版本标签  同一个镜像可以有多个标签Image Id  表示镜像的IDCreated 表示创建时间Size 表示文件大小命令功能命令行代码docker images -a列出本地所有的镜像docker images -q只显示镜像的ID2. docker search &lt;某个镜像的名称&gt; 搜索镜像其中Name 表示镜像的名称Description 表示镜像的说明stars 点赞的数量Officaial 表示是否是官方的 若是官方的 下方有一个[OK] 表示AutoMated 表示是否是自动构建的命令功能命令行代码分页查询镜像 查询N个docker search &lt;镜像名称&gt; --limit &lt; n &gt;Docker 自动构建镜像和非自动构建镜像的主要区别在于构建方式和更新机制。自动构建镜像是通过与代码托管平台（如 GitHub、GitLab、Bitbucket 等）集成，当代码仓库中的代码发生变化时，Docker Hub 会自动触发构建过程，生成新的镜像。自动构建镜像的好处是可以自动化构建和更新镜像，减少手动操作，提高效率。但是，由于构建过程是自动化的，可能会导致构建失败或者镜像质量不稳定的问题。非自动构建镜像则需要手动构建和更新，通常是通过 Dockerfile 文件来定义构建过程。这种方式可以更加灵活地控制构建过程和镜像内容，但需要手动操作，相对较为繁琐。总的来说，自动构建镜像适用于需要频繁更新的应用，非自动构建镜像适用于需要更加精细控制的应用。3. docker pull &lt;某个镜像的名称&gt; 下载/拉去镜像命令功能命令行代码拉去某个镜像的最新版本 latestdocker pull &lt; 镜像的名称 &gt;拉取某个镜像的指定版本(不要忘了冒号)docker pull &lt;镜像的名称&gt;:&lt; tag ID &gt;### 4. docker system df 查看镜像/容器/数据卷占用的空间5. docker rmi &lt;某个镜像的名字&gt; 删除镜像命令功能命令行代码强制删除镜像docker rmi -f &lt;镜像的ID&gt;删除多个镜像docker rmi -f &lt;镜像的ID&gt;:&lt;镜像ID&gt;删除全部镜像docker rmi -f ${docker images -qa}其中删除全部中使用到的是Shell脚本的特性，支持了参数的续传，可以将docker images -qa查询到的所有的镜像的ID传输给docker rmi -f执行，即就可以删除全部* Docker面试题：谈谈docker虚旋镜像是什么？1. 虚旋镜像长什么样子？在查询所有镜像仓库的时候可以看到的他 repository和TAG是&lt; none &gt;## 3.容器相关的命令1. 启动一个容器实例命令功能命令行代码启动一个容器，如启动一个nginx其中 -d表示后台运行这个容器，不占用前台的输入docker run --name &lt; 自定义这个容器的名字 &gt; -d &lt;镜像名称&gt;如，要启动一个Nginx容器，并且取名为MyNginx，且让他后台运行，不占用前台命令行，就有如下命令行docker run --name Mynginx -d nginx
## 
## 其中 --name 表示指定一个名称 后面是自定义的名称  
## -d 表示后台运行
## 最后是启动的这个镜像的名称2. 查询当前正在运行的容器命令功能命令行代码查询当前正在运行的容器docker ps查询罗列过的和运行过的所有容器docker ps -a查询最近创建的容器docker ps -l查询最近N个创建的容器docker ps -n &lt;查询的数量&gt;查询正在运行的容器的IDdocker ps -q其中docker ps -q查询当前正在运行的容器的ID可以和其他命令参数进行组合使用，如我们要查询所有运行过的容器就可以有如下的代码docker ps -aq3. 退出当前容器命令功能命令行代码退出当前容器exit  “使用这个命令退出容器会停止当前容器的运行”退出当前容器ctrl + p + q "使用这个命令退出容器不会停止容器的运行"4. 启动/停止/强制停止的容器命令功能命令行代码启动容器docker start &lt;容器的ID&gt;停止容器docker stop &lt;容器的ID&gt;强制停止容器docker kill &lt;容器的ID&gt;5. 删除已经停止容器在上面的例子中已经学到了删除镜像使用的命令是 docker rmi &lt;镜像的ID&gt;在删除容器的时候，命令就是docker rm &lt;容器的ID&gt;

# 强制删除容器 慎用 可以删除正在运行的容器 
dcker rm -f &lt;容器的ID&gt;注意: 容器保护机制让docker 只可以删除已经停止的镜像6. 查看容器启动的日志命令功能命令行代码查看容器日志docker logs &lt;容器的ID&gt;4. 交互式启动1. 什么是交互式启动？交互式启动如字面意思，有交互的启动，具体理解起来可以有如下例子：现在我们在docker 中创建了一个ubantu容器，因为Ubantu是Linux系统，这个Linux系统是有交互的，我们启动的时候可以使用如下命令docker run -it -name linux1 ubuntu 我们可以直接进入到这个系统中，并产生了一个交互式控制台输入，这个时候可以调用我们的linux命令进行操作这个容器，这就是交互思考？若我们退出的时候，应该如何再次进入，并产生交互控制台呢？这个时候就使用到了交互式启动运行命令功能命令代码创建容器的时候并返回一个交互控制台docker run -it --name&lt;自定义容器的名称&gt; &lt;要启动的镜像名称&gt;退出容器exit “如果是run -it启动时退出会关闭容器”退出容器ctrl + p + q "使用这个命令退出容器不会停止容器的运行"重新进入容器，并产生交互控制台(退出不会停止容器)docker exec -it &lt;容器的ID&gt; /bin/bash重新进入重启，并产生控制台(退出会停止容器)docker attach &lt;容器的ID&gt; /bin/bash</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/database/147">
<title>Redis基础入门</title>
<link>https://blog.anlucky.cn/index.php/programming/database/147</link>
<dc:date>2023-05-15T19:12:00+08:00</dc:date>
<description>Redis基础入门0.为什么Redis是单线程还这么快？redis 是将所有的数据全部放在内存中的，所以说使用单线程去操作效率就是最高的，多线程 （CPU上下文会切换：耗时的操作！！！），对于内存系统来说，如果没有上下文切换效率就是最高 的！多次读写都是在一个CPU上的，在内存情况下，这个就是最佳的方案！1. Redis介绍Redis 是一个开源（BSD许可）的，内存中的数据结构存储系统，它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构，如 字符串（strings）， 散列（hashes）， 列表（lists）， 集合（sets）， 有序集合（sorted sets） 与范围查询， bitmaps， hyperloglogs 和 地理空间（geospatial） 索引半径查询。 Redis 内置了 复制（replication），LUA脚本（Lua scripting）， LRU驱动事件（LRU eviction），事务（transactions） 和不同级别的 磁盘持久化（persistence）， 并通过 Redis哨兵（Sentinel）和自动 分区（Cluster）提供高可用性（high availability）。2. 关系型数据库和非关系型数据库关系型数据库（RDBMS）：表格 ，行 ，列泛指非关系型数据库的，随着web2.0互联网的诞生！传统的关系型数据库很难对付web2.0时代！尤其
是超大规模的高并发的社区！
暴露出来很多难以克服的问题，NoSQL在当今大数据环境下发展的十分迅速，Redis是发展最快的，而
且是我们当下必须要掌握的一个技术！
很多的数据类型用户的个人信息，社交网络，地理位置。这些数据类型的存储不需要一个固定的格式！
不需要多余的操作就可以横向扩展的 ！ Map&lt;String,Object&gt; 使用键值对来控制非关系型数据库(NOSQL)NOSQL：不仅仅是SQL
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储，列存储，文档存储，图形数据库（社交关系）
- 最终一致性，
- CAP定理和BASE （异地多活） 初级架构师！1. redis能做什么内存存储、持久化，内存中是断电即失、所以说持久化很重要（rdb、aof）效率高，可以用于高速缓存发布订阅系统地图信息分析计时器、计数器（浏览量！）2. redis特点特性多样的数据类型持久化集群事务3. Redis安装Linux中文官网：CRUG网站 (redis.cn)英文官网：Redis在官网中下载Redis的最新稳定版本即可，Redis是建议安装的，Linux可以下载.gz压缩文件，将压缩包解压到Linux服务器即可安装成功（我们可以解压到linux服务器中的/opt/目录中）4. 基本环境安装下载C++环境yum install gcc-c++
make
make install注意安装 redis执行make命令报错struct redisServer’没有名为‘sentinel_mode’的成员报错如图：1.解决安装 redis执行make命令报错struct redisServer’没有名为‘sentinel_mode’的成员查看gcc的版本是否在 5.3以上 命令：gcc -v\#升级到 5.3及以上版本命令：yum -y install centos-release-sclyum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutilsscl enable devtoolset-9 bash执行完毕之后去到src目录下执行make即可编译成功5. 启动Redis目前我使用的Linux centos 7 和Redis 6.X1.普通方式启动Redis使用 cd 命令进入Redis的安装目录，如：cd /opt/redis/src/查询目录下是否拥有 redis.server 如果有直接执行命令：./redis.server 若没有可以去目录 ·/usr/local/bin查找，然后执行./redis.server见到如图就是启动成功了我们会发现无法使用命令进行操作，这是因为redis的启动占用了当前线程，紧接着介绍第二种启动方式，也就是后台启动，是最常用的启动方式2. 后台线程方式启动Redis进入redis安装目录寻找配置后台启动配置文件redis.conf使用vi redis.conf命令编辑配置文件可以使用/ 命令查找对应的配置字母，修改如下配置值为yes使用命令wq 保存退出然后使用在安装目录使用命令./src/redis-server redis.conf （每个人的路径可能不一样，大概意思就是在启动方式1的基础上加上了配置文件的也叫配置文件启动方式）查询是否启动成功ps -ef | grep redis 找到redis的进程即是启动成功3. 关闭服务在可以直接使用Linux命令将线程kill掉，也可以在客户端工具中执行shutdown命令6. Redis客户端工具客户端工具的目录在Redis的安装目录中的/src/目录下，名称叫做redis.cli启动命令./redis.cli当输入命令更改为：127.0.0.1:6379&gt; 即为进入客户端工具成功退出可以使用ctrl+c直接强制退出可以命令ping 查看是否可以正常执行，返回PONG即表示成功7. Redis基础命令1. Redis的默认数据库redis默认有16个数据库，默认使用的是第0个数据库，可以使用select n 进行切换数据库select 3 # 切换数据库到第三个
DBSIZE # 查看数据库的大小2. 清空Redis数据库清空当前数据库：flushdb清空全部数据库的内容：flushall3. 查看所有的keykeys * # 查看所有的数据库8. Redis String(字符串)命令1. Setnx命令Redis Setnx（SET if Not eXists） 命令在指定的 key 不存在时，为 key 设置指定的值。语法：setnx &lt;k&gt; &lt;v&gt; # 如 key 为 name v 为 张三
setnx &lt;k&gt; &lt;v&gt; # 在第二次添加时，key为name时，不论值为什么都会添加失败2. getRange命令截取getRange 命令用于获取存储在指定 key 中字符串的子字符串。字符串的截取范围由 start 和 end 两个偏移量决定(包括 start 和 end 在内)。语法：# 设置一个k v数据
SET mykey &quot;This is my test key&quot;
getrange mykey 0 3
# 输出 &quot;This&quot;
getrange mykey 0 -1
# 输出 &quot;This is my test key&quot;3. Mset命令Mset 命令用于同时设置一个或多个 key-value 对。# 设置多个k v键值对
mset k1 &quot;Hello&quot; k2 &quot;World&quot;4. Setex命令Setex 命令为指定的 key 设置值及其过期时间。如果 key 已经存在， Setex 命令将会替换旧的值。setex k1 60 &quot;v1&quot;
# 设置 60秒过期的k1 值为 v15.set命令SET 命令用于设置给定 key 的值。如果 key 已经存储其他值， SET 就覆写旧值，且无视类型。# 设置一个键值对 k1 v1
set k1 v16. get命令返回对应 key 的value值，如果 key 不存在时，返回 nil。 如果 key 不是字符串类型，那么返回一个错误GET db # 不存在返回 nil7. getBit命令Getbit 命令用于对 key 所储存的字符串值，获取指定偏移量上的位(bit)。# 对不存在的 key 或者不存在的 offset 进行 GETBIT， 返回 0
 
redis&gt; EXISTS bit
(integer) 0
 
redis&gt; GETBIT bit 10086
(integer) 0
 
 
# 对已存在的 offset 进行 GETBIT
 
redis&gt; SETBIT bit 10086 1
(integer) 0
 
redis&gt; GETBIT bit 10086
(integer) 18. setBit命令Setbit 命令用于对 key 所储存的字符串值，设置或清除指定偏移量上的位(bit)。setbit bit 10086 1
getbit bit 100869.Decr命令Decr 命令将 key 中储存的数字值减一。如果 key 不存在，那么 key 的值会先被初始化为 0 ，然后再执行 DECR 操作。如果值包含错误的类型，或字符串类型的值不能表示为数字，那么返回一个错误。set k1 10

decr k1 # 返回 9
# 对不存在的key值进行decr

decr k2 # 不存在赋值初始为0 再减一所以返回 -1

set k3 &quot;hello&quot;

decr k3 # 存在但不是数值的key会返回一个错误10. decrBy命令Decrby 命令将 key 所储存的值减去指定的减量值。如果 key 不存在，那么 key 的值会先被初始化为 0 ，然后再执行 DECRBY 操作。如果值包含错误的类型，或字符串类型的值不能表示为数字，那么返回一个错误。本操作的值限制在 64 位(bit)有符号数字表示之内。set k1 100

decrby k1 20 # K1 减去20 返回 80
# 其余的特点和decr一样11. Strlen命令Strlen 命令用于获取指定 key 所储存的字符串值的长度。当 key 储存的不是字符串值时，返回一个错误set k1 &quot;hello word&quot;

strlen k1 # 返回k1的字符串长度 11

# 若k不存在返回 012. Msetnx命令Msetnx 命令用于所有给定 key 都不存在时，同时设置一个或多个 key-value 对。当所有 key 都成功设置，返回 1 。 如果所有给定 key 都设置失败(至少有一个 key 已经存在)，那么返回 0# 对不存在的 key 进行 MSETNX
 
MSETNX rmdbs &quot;MySQL&quot; nosql &quot;MongoDB&quot; key-value-store &quot;redis&quot; # 添加多个k v键值对
 
MGET rmdbs nosql key-value-store # 查询多个k v 键值对
1) &quot;MySQL&quot;
2) &quot;MongoDB&quot;
3) &quot;redis&quot;
 
 
# MSET 的给定 key 当中有已存在的 key
 
Msetnx rmdbs &quot;Sqlite&quot; language &quot;python&quot;  # rmdbs 键已经存在，操作失败

# 因为 MSET 是原子性操作，language 没有被设置
 
GET rmdbs     # rmdbs 也没有被修改 输出 MySQL
13. ncrby命令Incrby 命令将 key 中储存的数字加上指定的增量值。如果 key 不存在，那么 key 的值会先被初始化为 0 ，然后再执行 INCRBY 命令。如果值包含错误的类型，或字符串类型的值不能表示为数字，那么返回一个错误。本操作的值限制在 64 位(bit)有符号数字表示之内。# key 存在且是数字值
 
SET rank 50 # 添加一个k

INCRBY rank 20 # rank存在添加20 返回70

# key 不存在时

INCRBY counter 30 # 不存在，初始化0返回30
 
# key 不是数字值时
SET book &quot;long long ago...&quot;
INCRBY book 200 # book 值不是数字，报错14. Incrbyfloat命令Incrbyfloat 命令为 key 中所储存的值加上指定的浮点数增量值。如果 key 不存在，那么 INCRBYFLOAT 会先将 key 的值设为 0 ，再执行加法操作。# 值和增量都不是指数符号
SET mykey 10.50 # 设置myke 值为10.50
 
incrbyfloat mykey 0.1 # mykey的值增加0.1
 
# 值和增量都是指数符号
 
SET mykey 314e-2 # 设置mykey值为 314e-2

GET mykey  # 用 SET 设置的值可以是指数符号&quot;314e-2&quot;
 
incrybyfloat mykey 0      # 但执行 INCRBYFLOAT 之后格式会被改成非指数符号
&quot;3.14&quot;
 
# 可以对整数类型执行
 
SET mykey 3

INCRBYFLOAT mykey 1.1 # 对整数类型数据增加1.1
# 后跟的 0 会被移除
SET mykey 3.0
GET mykey                                   # SET 设置的值小数部分可以是 0 显示 &quot;3.0&quot;
 
INCRBYFLOAT mykey 1.000000000000000000000    # 但 INCRBYFLOAT 会将无用的 0 忽略掉，有需要的话，将浮点变为整数
GET mykey 返回 &quot;4&quot;15.Setrange命令Setrange 命令用指定的字符串覆盖给定 key 所储存的字符串值，覆盖的位置从偏移量 offset 开始。set k1 &quot;hello&quot; #设置k1 的值为 hello 
setrange k1 1 &quot;333&quot; # 将k1 从索引2开始替换三个位数的字符，会替换掉ell 会返回h333o16. psetex命令Psetex 命令以毫秒为单位设置 key 的生存时间。psetex的使用方法和setex的使用方法一样，只不过设置的过期时间是以毫秒为单位17. Append 命令Append 命令用于为指定的 key 追加值。如果 key 已经存在并且是一个字符串， APPEND 命令将 value 追加到 key 原来的值的末尾。如果 key 不存在， APPEND 就简单地将给定 key 设为 value ，就像执行 SET key value 一样。append k1 &quot;test&quot; # 当k1不存在的时候会创建k1 值为 test等同于 set k1 &quot;test&quot;

set k2 &quot;demo&quot;
appent k2 &quot;add&quot; # 当k2存在的时候，会将字符添加到原来的值后面，会返回字符长度18. getSet命令Getset 命令用于设置指定 key 的值，并返回 key 旧的值。set name &quot;hello&quot; # 设置一个初始k
getset name &quot;word&quot; # 会返回显示hello，然后将name的值设置为word
get name #这个时候name的值是word19.  MgetMget 命令返回所有(一个或多个)给定 key 的值。 如果给定的 key 里面，有某个 key 不存在，那么这个 key 返回特殊值 nilset k1 &quot;v1&quot; # 添加
set k2 &quot;v2&quot; # 添加
mget k1 k2 k3 # 获取多个 不存在返回 nil

1) &quot;v1&quot;
2) &quot;v2&quot;
3) (nil)
20. Incr命令Incr 命令将 key 中储存的数字值增一。如果 key 不存在，那么 key 的值会先被初始化为 0 ，然后再执行 INCR 操作。如果值包含错误的类型，或字符串类型的值不能表示为数字，那么返回一个错误。本操作的值限制在 64 位(bit)有符号数字表示之内。set k1 20 
incr k1  # k1 加一
get k1 # 返回219.Redis 哈希(Hash)命令1.Hmset命令和Hset命令Hmset 命令用于同时将多个 field-value (字段-值)对设置到哈希表中。此命令会覆盖哈希表中已存在的字段。如果哈希表不存在，会创建一个空哈希表，并执行 HMSET 操作hset k1 f1 &quot;k1-vf1&quot; f2 &quot;k1-vf2&quot;  # hmset和hset使用方式一样，不过hmset在官方文档中说已经弃用
hget k1 f1 # hget2. Hmget命令Hmget 命令用于返回哈希表中，一个或多个给定字段的值。如果指定的字段不存在于哈希表，那么返回一个 nil 值。hset k1 f1 &quot;k1-vf1&quot; f2 &quot;k1-vf2&quot;
hmget k1 f1 f2 # hmget可以通过多个fild 获取到值3. Hgetall命令命令用于返回哈希表中，所有的字段和值。在返回值里，紧跟每个字段名(field name)之后是字段的值(value)，所以返回值的长度是哈希表大小的两倍。hset k1 fd1 fdv1 fd2 fdv2 fd3 fdv3 #添加数据

hgetall k1 # 查询数据

# 输出结果 前面是fdkey 后面跟着他的fdvalue
1) &quot;fd1&quot;
2) &quot;fdv1&quot;
3) &quot;fd2&quot;
4) &quot;fdv2&quot;
5) &quot;fd3&quot;
6) &quot;fdv3&quot;4. Hget命令命令用于返回哈希表中指定字段的值。# 字段存在
HSET site redis redis.com

HGET site redis # 输出结果&quot;redis.com&quot; 不存在返回nil5. Hexists命令用于查看哈希表的指定字段是否存在。如果哈希表含有给定字段，返回 1 。 如果哈希表不含有给定字段，或 key 不存在，返回 0 。hset k1 fd1 fdv1 fd2 fdv2 fd3 fdv3 #添加数据

hgetall k1 # 查询数据

hexists k1 fd4 # 不存在返回0
hexists k1 fd1 # 存在返回 16. Hincrby命令Hincrby 命令用于为哈希表中的字段值加上指定增量值。增量也可以为负数，相当于对指定字段进行减法操作。如果哈希表的 key 不存在，一个新的哈希表被创建并执行 HINCRBY 命令。如果指定的字段不存在，那么在执行命令前，字段的值被初始化为 0 。对一个储存字符串值的字段执行 HINCRBY 命令将造成一个错误。本操作的值被限制在 64 位(bit)有符号数字表示之内。hset k1 f1 20 # 添加一个key
hincrby k1 f1 1 # 给k1哈希表中的f1的值增加1
# 若不是数字类型，那么将报错7. Hlen命令Hlen 命令用于获取哈希表中字段的数量。hset k1 f1 v1 f2 v2
hlen k1 #返回 2 对应的k1哈希表中有两个字段分别是f1 f2,不存在返回08. Hdel命令Hdel 命令用于删除哈希表 key 中的一个或多个指定字段，不存在的字段将被忽略。hset k1 f1 v1 f2 v2 #添加初始数据
hdel k1 f2 # 删除k1哈希表中的f2字段 返回 1成功
hget k1 f1 # 返回v1
hget k1 f2 # 返回 nil
hdel k1 f3 #返回 0删除失败9.Hvals命令Hvals 命令返回哈希表所有字段的值。hset k1 f1 v1 f2 v2
hvals k1 # 返回 v1 v210. Hincrbyfloat命令Hincrbyfloat 命令用于为哈希表中的字段值加上指定浮点数增量值。如果指定的字段不存在，那么在执行命令前，字段的值被初始化为 0 。hset k1 f1 20.1
hincrbyfloat k1 f1 1.1 # 返回 21.211. Hkeys命令Hkeys 命令用于获取哈希表中的所有字段名。hset k1 f1 v1 f2 v2
hkeys k1 # 返回 f1 f212. Hsetnx 命令Hsetnx 命令用于为哈希表中不存在的的字段赋值 。如果哈希表不存在，一个新的哈希表被创建并进行 HSET 操作。如果字段已经存在于哈希表中，操作无效。如果 key 不存在，一个新哈希表被创建并执行 HSETNX 命令。hsetnx k1 f1 v1
hsetnx k1 f1 v2 # 操作无效，已经存在忽略
hget k1 f1 # 值是 v110. 其他命令其他命令可以进入官网进行自测官网：Redis命令中心（Redis commands） -- Redis中国用户组（CRUG）更多Redis资料：Redis 教程 | 菜鸟教程 (runoob.com)</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/133">
<title>IDEA中如何将方法的注释一并重写</title>
<link>https://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="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>
<item rdf:about="https://blog.anlucky.cn/index.php/programming/116">
<title>客户端容器(浏览器)</title>
<link>https://blog.anlucky.cn/index.php/programming/116</link>
<dc:date>2023-04-26T21:38:00+08:00</dc:date>
<description>1. 浏览器架构的演进单进程架构：所有的模块运行在同一个进程里，包含网络、插件、JavaScript运行环境等多进程架构：主进程、网络进程、渲染进程、GPU进程、插件进程面向服务架构：将原来的UI、数据库、文件、设备、网络等作为一个独立的基础服务2. 浏览器架构对比架构类型扩展性安全性稳定性流畅度单进程架构扩展性低，所有模块运行在统一进程里，访问同一块内存区域，数据没有隔离，新增模块可能会影响原有功能安全性低，三方插件可直接访问操作系统里任意资源低，上方插件漏洞或者某个tab页面中的JavaScript脚本出现问题可能会导致浏览器崩溃卡顿，所有页面运行在同一进程中，开启多个页面时明显卡顿多进程架构扩展性中等，各进程分配独立的内存区域，有些进程功能较大，耦合度高安全性高，运行在独立的沙箱中，不能访问系统敏感资源高，进程相互隔离，当一个页面或者插件崩溃时，不会影响其他的进程流畅，每个页面运行在独立的渲染进程中，充分利用系统的资源面向服务架构扩展性高，服务模块划分更细致，更内聚，耦合性低，易于扩展安全性高，运行在独立沙箱中，不能访问系统敏感资源高，进程相互隔离，当一个页面或者插件崩溃时，不会影响其他的进程流畅，每个页面运行在独立的渲染进程中，充分利用系统的资源3. 浏览器架构-多进程分工进程名称进程描述浏览器（主进程）主要负责页面暂时逻辑，用户交互，子进程管理，（地址栏，书签，前进，后退，收藏夹等）GPU进程负责UI绘制，包含整个浏览器全部UI网络进程网络服务进程，负责网络资源加速标签页（渲染进程）控制页面内的所有内容，将HTML，Css 和 JavaScript转换为用户可交互网页插件进程控制网站运行的插件，比如flash等其他进程如：storage/network/等4. 浏览器内核内核浏览器J S 引擎补充说明TridentIE 4 -- 11JScript，Chakra出生于1994年，IE8以前使用 JScript引擎，IE9开始使用Chakra引擎GeckoFirefoxSpideMonkeyGecoko内核主要用在Firfox浏览器上，同时是一个跨平台的内核，支持在windows、BSD、Linux、Mac OS中使用WebkitSafari、Chrome、Android浏览器JavaScriptCore由Apple公司技术团队开发，并在2005年开源EdgeEdgeChakra2015年由微软发布，用于Edge浏览器，由于性能较差，2018年微软将Edge浏览器内核迁移到ChromiumBlinkChrome，OperaV8Google 基于WebKit开发的内核，在webkit的基础上加入多线程，沙箱等技术，于2013年开源Trident+WebKit(Blink)国产浏览器QQ，360、搜狗、UC等都有都有早期银行系统都是在IE上进行开发，想要支持银行系统就切换到Trident内核，想要体验就切换到Webkit内核5. 渲染进程-多线程架构内部是多线程，主要负责渲染页面，脚本执行，事件处理，网络请求等线程功能JS 引擎负责解析 JS 脚本，运行 JS 程序，每个渲染进程下面只有一个 JS 引擎线程，与GUI渲染线程互斥，如果JS任务执行事件过长，会导致页面卡顿GUI渲染负责渲染浏览器界面，解析HTML、CSS、构建Dom树和render树，布局，绘制和 JS 引擎线程互斥，GUI更新会在 JS引擎空闲时立即执行定时器触发定时器所在线程，setTimeout、SetInterval计时完毕后，将回调添加到事件队列，等待JS引擎执行网络线程在XHR、Fetch等发起请求后新开一个网络线程请求，如果设置了回调函数，在状态变更时候，将回调放入事件队列，等待 JS引擎执行事件触发由宿主环境提供，用于控制事件循环，不断地从事件队列里取出任务执行6. JS引擎和渲染引擎对比JS引擎会将 Js文件解释翻译为字节码，然后解释执行，或者解析为机器码，直接执行渲染引擎是将HTML代码，CSS代码分别通过他们各自的解释器进行解析为DOM树和CSSOM树，然后合成为Render树，然后渲染到页码上他们使用Bridge通信，桥接方式7. Chrome运行原理1. 输入处理用户URL框输入内容后，UI线程会判断输入的是一个URL地址，还是一个Query查询条件如果是URL，直接请求站点资源如果是Query，将输入发送给搜索引擎2. 开始导航用户按下回车，UI线程通知网络线程发起一个请求，来获取站点内容请求过程中，Tab处于Loading状态，就是转圈圈的加载状态3. 读取响应网络线程接收到HTTP响应后，先检查响应头的媒体类型，如果响应类型是媒体类型，如：HTML，浏览器会将内容交给渲染进程处理如果拿到的是其他类型，如ZIP，EXE，会交给下载管理器处理4. 寻找渲染进程网络线程做完所有的检查后，会告知主进程数据已经准备完毕，主进程确认后为这个站点寻找一个渲染进程主进程通过IPC消息告知进程去处理本次导航渲染进程开始接收数据并告知进程自己已经开始处理，导航结束，进入文档加载阶段，除此之外，还需要加载一些子资源，比如一些图片，CSS样式以及JavaScript脚本8. 前端性能优化1. 首屏优化压缩，分包，删除无用代码（体积小点）静态资源分离（同源的话会带Cookie，CDN不带Cookie所以会快点）Js 脚本非阻塞加载（JS放到代码底部，渲染是从上往下，因为渲染引擎和 JS 引擎互斥所以先执行渲染再执行JS会快点）缓存策略SSR预置loading，骨架屏2. 渲染优化GPU加速减少回流、重绘离屏渲染懒加载3. Js优化防止内存泄漏循环尽早break合理使用闭包减少Dom访问防抖、节流web workers9. 跨端容器1. 为什么需要跨端跨端开发成本低，效率低一致性体验，跨端的项目客户在多种客户端体验一样前端开发生态2. 跨端方案web view小程序RN 、WeeXLynxFlutter1. 跨端方案-Web Viewweb View 即网页视图，用于加载网页URL，并展示其内容的控件可以在内嵌移动端App内，实现前端混合开发，大多数混合框架都是基于WebView的二次开发，比如Lonic、Cordova2. 使用Web View的优势一次开发，处处使用，学习成本低随时发布，即使更新，不用下载安装包移动设备性能不断提升，性能有保障通过JSBirdge和原生系统交互，实现复杂功能3. WebView使用原生能力JavaScript调用NativeAPI注入：Native获取JavaScript环境上下文，对其挂载的对象或者方法进行拦截使用WebView Url Scheme跳转拦截IOS上 window.webkit.messageHandler 直接通信Native 调用JavaScript直接通过webview暴漏的API执行JS代码IOS webview.stringByEvaluatingJavaScriptFormStringAndroid webview.evaluate.JavaScript10. 跨端容器-小程序微信、支付宝、百度、小米直达号都有小程序渲染层-webview（渲染方案）双线程，多WebView架构数据通信，native转发11. 跨端容器-React Native/Weex原生组件渲染依赖React / vue框架virtual Dom（JS对象，对DOM描述）JSBridge 通信</description>
</item>
</rdf:RDF>