博客系统(SpringBoot项目)

文章目录

  • 一、项目开发的流程
  • 二、项目开发
    • 2.1 准备工作
    • 2.2 开发公共模块:把能写的先写了
      • 什么是公共模块
      • model层
      • mapper层
      • 定义统一返回结果
      • 统一异常处理
    • 2.2 博客列表页
    • 2.3 更改显示的时间
    • 2.4 博客详情页
    • 2.5 登录
      • Session式登录方法分析
      • 使用Token来实现登录
    • 2.6 强制登录
    • 2.7 获取用户信息和作者信息
    • 2.8 实现用户退出
    • 2.9 实现发布博客
    • 2.10 修改返回格式
    • 2.11 删除/编辑博客
      • 出现删除和编辑按钮
      • 编辑操作
      • 删除操作
  • 三、Token
    • 3.1 什么是Token + 与Session的区别 + 优缺点
    • 3.2 JWT令牌介绍
    • 3.3 如何使用JWT令牌实现Token
  • 四、有无状态
  • 五、加密/加盐
  • 六、部署服务
    • 6.1 部署的流程
    • 6.2 搭建Java部署环境
      • 安装jdk
      • 安装mysql
    • 6.3 多平台配置
    • 6.4 部署

一、项目开发的流程

  1. 产品经理定下来需求文档

  2. 了解需求,确认需求有无问题:如果需求文档有问题,提出来让产品经理去修改

  3. 方案设计:包括了【接口设计】、【数据库设计】、【架构图】、【流程图】等等

    • 文档设计:注意【方案设计】和【接口设计】都是要单独出一个文档的,方案设计里可以放一个【接口设计】的链接
      • 区别:方案设计是给后端开发团队内部人员看的,接口设计是给其他团队看的
      • 接口文档:告诉别人如何去使用该工具,需不需要引入包,接口是什么,每个字段都是什么含义,相当于一个对外的说明书
      • 方案设计
        在这里插入图片描述
  4. 开发

  5. 测试

  6. 联调:联动其他部门调试,也是一种形式的测试

  7. 提交测试:将结果提交给测试人员

  8. 上线

二、项目开发

2.1 准备工作

  1. 创建Spring Boot项目
  2. 修改Spring Boot配置文件内容
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/mycnblog?characterEncoding=utf8&useSSL=false
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  mapper-locations: classpath:mapper/*Mapper.xml
  configuration: # 配置打印 MyBatis 执行的 SQL
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true  #自动驼峰转换

logging:
  file:
    name: logs/springboot.log
  1. 创建数据库
-- 建表SQL
create database if not exists java_blog_spring charset utf8mb4;
-- ⽤⼾表
DROP TABLE IF EXISTS java_blog_spring.user;
CREATE TABLE java_blog_spring.user(
                                      `id` INT NOT NULL AUTO_INCREMENT,
                                      `user_name` VARCHAR ( 128 ) NOT NULL,
                                      `password` VARCHAR ( 128 ) NOT NULL,
                                      `github_url` VARCHAR ( 128 ) NULL,
                                      `delete_flag` TINYINT ( 4 ) NULL DEFAULT 0,
                                      `create_time` DATETIME DEFAULT now(),
                                      `update_time` DATETIME DEFAULT now(),
                                      PRIMARY KEY ( id ));
-- 博客表
drop table if exists java_blog_spring.blog;
CREATE TABLE java_blog_spring.blog (
                                       `id` INT NOT NULL AUTO_INCREMENT,
                                       `title` VARCHAR(200) NULL,
                                       `content` TEXT NULL,
                                       `user_id` INT(11) NULL,
                                       `delete_flag` TINYINT(4) NULL DEFAULT 0,
                                       `create_time` DATETIME DEFAULT now(),
                                       `update_time` DATETIME DEFAULT now(),
                                       PRIMARY KEY (id))
    ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '博客表';
-- 新增⽤⼾信息
insert into java_blog_spring.user (user_name, password,github_url)values("zhangs","123456", "xxx");
insert into java_blog_spring.user (user_name, password,github_url)values("lisi", "123456", "xxx");
insert into java_blog_spring.blog (title,content,user_id) values("第⼀篇博客","11",1);
insert into java_blog_spring.blog (title,content,user_id) values("第⼆篇博客","22",2);
  1. 添加前端代码

2.2 开发公共模块:把能写的先写了

什么是公共模块

在这里插入图片描述

model层

在这里插入图片描述

mapper层

  1. 写mapper时就需要对功能进行梳理了,看我们的需求需要哪一些数据库操作
    • 关于查询用户信息:根据用户名查询用户信息,比对密码是否正确
      • 为什么不建议通过用户名和密码同时去查:我们不建议让密码通过数据库的查询,而且密码可能会经过加密之类的处理,出现了SQL注入的情况也不太好处理
    • 可以通过单元测试来判断代码有无错误
@Mapper
public interface UserInfoMapper {
    @Select("select * from user where user_name = #{userName} and delete_flag = 0")
    UserInfo queryByName(String userName);
    @Select("select * from user where id = #{id} and delete_flag = 0")
    UserInfo queryById(Integer id);
}
@Mapper
public interface BlogInfoMapper {
    @Select("select * from blog where delete_flag = 0")
    List<BlogInfo> queryBlogList();

    @Select("select * from blog where id = #{id} and delete_flag = 0")
    BlogInfo queryById(Integer id);

    @Update("update blog set title = #{title}, content = #{content} where id  = #{id}")
    Integer updateBlog(BlogInfo blogInfo);

    @Update("update blog set delete_flag = 1 where id = #{id}")
    Integer deleteBlog(Integer id);

    @Insert("insert into blog(title, content, user_id) values (#{title}, #{content}, #{userId}")
    Integer insertBlog(BlogInfo blogInfo);
}

定义统一返回结果

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

统一异常处理

在这里插入图片描述

2.2 博客列表页

  1. 前端代码
    在这里插入图片描述
  2. 后端代码

Controller层

@RestController
@RequestMapping("/blog")
public class BlogController {
    @Autowired
    private BlogService blogService;

    @RequestMapping("/queryBlogList")
    public List<BlogInfo> queryBlogList(){
        List<BlogInfo> res = blogService.queryBlogList();
        return res;
    }
}

Service层

@Service
public class BlogService {
    @Autowired
    private BlogInfoMapper blogInfoMapper;

    public List<BlogInfo> queryBlogList(){
        List<BlogInfo> res = blogInfoMapper.queryBlogList();
        return res;
    }
}

Mapper层

@Mapper
public interface BlogInfoMapper {
    @Select("select * from blog where delete_flag = 0")
    List<BlogInfo> queryBlogList();
}

2.3 更改显示的时间

在这里插入图片描述

2.4 博客详情页

  1. 前端代码
    在这里插入图片描述
  2. 后端代码

Controller层

@RestController
@RequestMapping("/blog")
public class BlogController {
    @Autowired
    private BlogService blogService;

    @RequestMapping("/queryBlogDetail")
    public BlogInfo queryBligDetail(Integer blogId){
        BlogInfo res = blogService.queryBlogDetail(blogId);
        return res;
    }

}

Service层

@Service
public class BlogService {
    @Autowired
    private BlogInfoMapper blogInfoMapper;

    public BlogInfo queryBlogDetail(Integer id){
        BlogInfo res = blogInfoMapper.queryById(id);
        return res;
    }
}

Mapper层

@Mapper
public interface BlogInfoMapper {
    @Select("select * from blog where id = #{id} and delete_flag = 0")
    BlogInfo queryById(Integer id);

}

2.5 登录

Session式登录方法分析

  1. 传统思路
    • 前端输入账号、密码后,后端进行校验
      • 后端校验成功后,存储Session,并返回Cookie
    • 前端进行页面跳转,后续访问时,会携带Cookie(里面有sessionId)。后端通过sessionId去服务器里存储的Session里取值,判断用户是否登录
  2. 传统思路存在的问题
    • 修改代码后,需要重新登录
      • 因为Session存储在服务器内存中,所以当服务器重启后,Session就丢失了。用户如果正在使用程序,且遇到了服务器重启,此时如果要再想用,就需要重新登录,这很影响用户体验
    • 是单机部署:现在的服务大多不是单机部署,而是多机部署
      • 单机部署存在的问题:服务全部部署在一台机器上,当用户流量太大或者其他例如被黑客黑了的原因,导致这台机器挂了,那所有的服务就都无了

      • 关于多机部署
        在这里插入图片描述

      • 登录操作使用多机部署的情况

        • 情境
          (1)用户第一次请求被分配到了服务器1,Session此时存储在了服务器1

          (2)用户第二次请求,请求被分配到了服务器2,服务器2由于没有用户的Session,被认为没有登录,无法提供后续服务,要求再次登录。

        • 解决方法
          (1)Session存储在一个公用机器或者是缓存等地方,如Redis
          在这里插入图片描述

          (2)使用token(带有一定信息的字符串)

使用Token来实现登录

  1. 思路
    • 根据用户名和密码,验证密码是否正确
      • 如果密码正确,后端生成Token,并返回给前端(Token由客户端保存)
      • 前端存储时,可以把Token放在Cookie里,也可以放在本地存储里(浏览器给每个前端都保留了一个小空间,用来保存数据)
    • 后续访问时,Token一般会放在Http请求的header中(也可以作为一个参数),由后端校验Token的合法性
  2. Controller层
    • 思路
      • 检查参数
      • 查询用户是否存在,如果不存在,直接返回
      • 密码是否正确
      • 将用户的信息存储到token里,如用户名、密码等信息
@RequestMapping("/user")
@RestController
public class UserController {
    @Autowired
    private UserService userService;

    @RequestMapping("/login")
    public Result login(String userName, String password){
        if (!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)){
            return Result.fail("用户名或密码为空");
        }

        UserInfo userInfo = userService.queryByName(userName);
        if (userInfo == null || userInfo.getId() < 0){
            return Result.fail("用户不存在");
        }

        if (!password.equals(userInfo.getPassword())){
            return Result.fail("密码错误");
        }

        //生成Token并返回,账户和密码都正确的情况
        Map<String, Object> claim = new HashMap<>();
        claim.put("id", userInfo.getId());
        claim.put("name", userInfo.getUserName());
        String token = JwtUtils.genToken(claim);
        return Result.success(token);
    }

}
  1. 帮助生成token的代码
public class JwtUtils { //在utils包里
    private static final long expiration = 30 * 60 * 1000;
    private static final String secreString = "abJPV1XoZl11HrSHF0eSIfonkgMYwAk++RYhR5+i6RU=";
    private static final Key key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secreString));

    public static String genToken(Map<String ,Object> claim){
        String token = Jwts.builder()
                .setClaims(claim)
                .setExpiration(new Date(System.currentTimeMillis() + expiration))
                .signWith(key)
                .compact();

        return token;
    }

}
  1. Service层
@Service
public class UserService {
    @Autowired
    private UserInfoMapper userInfoMapper;

    public UserInfo queryByName(String username){
        return userInfoMapper.queryByName(username);
    }
}
  1. mapper层
@Mapper
public interface UserInfoMapper {
    @Select("select * from user where user_name = #{userName} and delete_flag = 0")
    UserInfo queryByName(String userName);
}
  1. 效果
    在这里插入图片描述
  2. 前端代码
    • localStorage.setItem():存储到内存里
      在这里插入图片描述
      在这里插入图片描述

2.6 强制登录

  1. 关于重复登录的问题

    • 后端只是生成和验证Token,存储是交给前端的。此时,哪怕后端重启了,因为存储方不在后端,除非我们把前端存储的token删掉,否则用户不需要重复登录
    • 在多机环境下,Token可以正常工作
  2. 流程
    在这里插入图片描述

  3. 登录的拦截器
    在这里插入图片描述

  4. 让拦截器生效
    在这里插入图片描述

  5. 让前端在Header里面发送token
    在这里插入图片描述

  6. 前端未登录时的逻辑
    在这里插入图片描述

2.7 获取用户信息和作者信息

  1. 实现方式
    • 方式一:如果页面需要的信息较少,且是固定不变的,就可以把这些信息存储到token里,直接从token获取
      • 不建议这种方式,因为这点无法保证,比如用户信息就是会发生变化的,虽然一旦发生变化,我们只需要更改Token即可,但这里其实涉及到了服务边界的问题
    • 方式二:从token中获取用户ID,根据用户ID,获取用户信息
      • 如果用户信息较少,可以把信息存储在token里。此处我们在Token里面存储了id,可以通过id从数据库中查到用户的对应信息
  2. 后端代码

Controller层

@RequestMapping("/user")
@RestController
@Slf4j
public class UserController {
    @Autowired
    private UserService userService;

    @RequestMapping("/getUserInfo")
    public UserInfo getUserInfo(HttpServletRequest request){
        String token = request.getHeader("user_token");
        Integer userId = JwtUtils.getUserIdFromToken(token);
        if (userId == null){
            return null;
        }

        return userService.queryById(userId);
    }

    @RequestMapping("/getAuthorInfo")
    public UserInfo getAuthorInfo(Integer blogId){
        return userService.getAuthorInfo(blogId);
    }
}

Service层

@Service
public class UserService {
    @Autowired
    private UserInfoMapper userInfoMapper;

    public UserInfo queryById(Integer userId) {
        return userInfoMapper.queryById(userId);
    }

    public UserInfo getAuthorInfo(Integer blogId) {
        BlogInfo blogInfo = blogInfoMapper.queryById(blogId);
        if (blogInfo == null || blogInfo.getUserId() < 0){
            return null;
        }

        return userInfoMapper.queryById(blogInfo.getUserId());
    }
}

Mapper层

@Mapper
public interface UserInfoMapper {
    @Select("select * from user where id = #{id} and delete_flag = 0")
    UserInfo queryById(Integer id);
}

Utils层
解析传来的token(从里面获取id)

@Slf4j
public class JwtUtils {
    public static Claims parseToken(String token){
        JwtParser build = Jwts.parserBuilder().setSigningKey(key).build();
        Claims claims = null;
        try{
            claims = build.parseClaimsJws(token).getBody();
        }catch (Exception e){
            log.info("解析token失败,token:" + token);
        }
        return claims;
    }

    public static Integer getUserIdFromToken(String token){
        Claims claims = parseToken(token);
        if (claims == null){
            return null;
        }
        return (Integer) claims.get("id");
    }

}

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

  1. 前端代码
    • 拿到blogId
      • 直接从query string里拿,location.href
      • 设置一个隐藏框,把blogId放进去
getUserInfo();
function getUserInfo() {
    $.ajax({
        type: "get",
        url: "/user/getUserInfo
        success: function(result){
            if (result.code == 200 && result.data != null){
                var user = result.data;
                $(".left .card h3").text(user.userName);
                $(".left .card a").after("href", user.githubUrl);
            }else{
                location.href = blog_login.html;
            }
        }
    });
}
getUserInfo();
function getUserInfo() {
    $.ajax({
        type: "get",
        url: "/user/getAuthorInfo" + location.search,
        success: function(result){
            if (result.code == 200 && result.data != null){
                var user = result.data;
                $(".left .card h3").text(user.userName);
                $(".left .card a").after("href", user.githubUrl);
            }else{
                location.href = blog_login.html;
            }
        }
    });
}

因为两个代码只改动了url,所以我们可以把这段代码提到 common.js 即可,后续直接传入 url 即可

在这里插入图片描述

2.8 实现用户退出

  1. 实现方式:把前端保存的token删除即可,注意因为很多页面都需要该功能,故直接放在common.js里,到时候直接引用即可
function  logout(){
    localStorage.removeItem("user_token");
    alert("注销成功");
    location.href = "blog_login.html";
}

2.9 实现发布博客

  1. 实现方式
    • editor.md:editor.md 是一个开源的页面 markdown 编辑器组件,使用时引入对应依赖就行了
  2. 后端代码

Controller层

@Slf4j
@RestController
@RequestMapping("/blog")
public class BlogController {
    @Autowired
    private BlogService blogService;

    @RequestMapping("/publishBlog")
    public Result publishBlog(String title, String content, HttpServletRequest request){
        String token = request.getHeader("user_token");
        Integer userId = JwtUtils.getUserIdFromToken(token);

        if (userId == null || userId < 0){
            log.info("传来的用户id有误,为: " + userId);
            return Result.fail("用户未登录", false);
        }

        BlogInfo blogInfo = new BlogInfo();
        blogInfo.setUserId(userId);
        blogInfo.setTitle(title);
        blogInfo.setContent(content);
        blogService.insertBlog(blogInfo);
        return Result.success(true);
    }
}

Service层

@Service
public class BlogService {
    @Autowired
    private BlogInfoMapper blogInfoMapper;

    public Integer insertBlog(BlogInfo blogInfo) {
        if (blogInfo == null){
            return 0;
        }
        return blogInfoMapper.insertBlog(blogInfo);
    }
}

Mapper层

@Insert("insert into blog(title, content, user_id) values (#{title}, #{content}, #{userId})")
Integer insertBlog(BlogInfo blogInfo);
  1. 前端代码
function submit() {
    $.ajax({
        type:"post",
        url: "/blog/publishBlog",
        data:{
            title:$("#title").val(),
            content:$("#content").val()
        },
        success:function(result){
            if(result.code==200 && result.data==true){
                location.href = "blog_list.html";
            }else {
                alert(result.error);
            }
        }
    });
}

2.10 修改返回格式

在这里插入图片描述

2.11 删除/编辑博客

出现删除和编辑按钮

  1. 实现效果:只有是作者本人(登录用户是作者)才能进行【编辑】和【删除】

    • 实现方式
      • 方式一:单独写一个用来判断 “当前登录用户是否等于作者” 的接口,返回true/false
        推荐,因为这符合接口的单一原则,让一个接口功能的维度更细一点。
      • 方式二:用一个变量标记当前登录用户是否为作者
        该方法不推荐,但实现起来比较简单
  2. 后端代码
    在这里插入图片描述

  3. 前端代码
    在这里插入图片描述

编辑操作

  1. 更新的时候,获取当前帖子的内容,方便修改
    在这里插入图片描述
  2. 编辑操作的后端代码
    在这里插入图片描述
  3. 编辑操作的前端代码
    在这里插入图片描述

删除操作

在这里插入图片描述

三、Token

3.1 什么是Token + 与Session的区别 + 优缺点

  1. 什么是Token
    • 是客户端进行访问时携带的身份标识,就像人的身份证一样,不能伪造(但是其他人能看到),不是加密
    • 令牌和token可以认为是一个东西
  2. Token与Session的区别
    • Session
      (1)走到哪都会用户都会携带身份证,酒店不会携带身份证,Session相当于把身份证放在了酒店,然后用户一直在该酒店里住

      (2)第一次入住时,用户把身份信息登记在酒店上,酒店会存储下来,往后用户再住的时候,酒店因为已经保存了我们的身份信息,可以直接入住,无需再登记

    • Token
      (1)用户不会在一个酒店上吊死,会去多个酒店入住,也因此不可能每个酒店都存有用户的身份信息

      (2)此时用户每到一个酒店,就会展示身份证,酒店看到身份信息后就知道我们是谁了

  3. 令牌的优缺点
    • 优点:解决了集群环境下的的认证问题 + 令牌是在用户这边存储的,减轻了服务器的存储压力
    • 缺点:需要自己生成、传递、校验

3.2 JWT令牌介绍

  1. 服务器 VS Token:服务器具备的功能就是校验身份信息,因为Token无法伪造,所以服务器需要有判断Token是否为真的能力
  2. 关于JWT令牌:Token的实现方式有很多,此处我们用开源的【JWT令牌】网址
  3. 存储位置:Cookie也是存储在客户端的一个信息,里面除了存Token,还存了些别的信息
  4. JWT介绍
    在这里插入图片描述

3.3 如何使用JWT令牌实现Token

  1. 引入依赖:这个依赖jdk8和17都能支持
<!--		jwt依赖-->
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>
  1. 生成Token
    在这里插入图片描述

  2. 解析Token
    在这里插入图片描述
    在这里插入图片描述

  3. 其他
    在这里插入图片描述

四、有无状态

  1. 有状态 VS 无状态:和HTTP的有无状态是一样的
    • 有状态:当前状态与上一次的进度有关,是衔接过来的。如接力赛,下一棒的人要接过上一棒才能行动。
    • 无状态:当前状态与上一次的进度无关,从中间的任何一个状态上去都可以无缝衔接,如自由跑,可以选择从任何一个起点开始跑,不用管上一次是在哪里起跑的。
  2. 关于服务的有无状态:我们去做服务时,要尽可能保证服务是【无状态】的
    • 服务有状态:服务器重启后有记忆一些东西
    • 服务无状态:服务器重启后没有记忆一些东西
  3. 关于Session 和 Token:Session存储在服务器,服务器有记忆功能,是有状态的; Token存储在客户端,是无状态的

五、加密/加盐

  1. 什么是加密:我们需要对诸如“密码”、“身份证号”……等敏感信息加密,这样即使数据库被入侵了,别人也没办法知道数据是什么
  2. 如何加密:需要使用密码算法对保存的明文进行加密,使其变成密文
    在这里插入图片描述
    • 对称密码算法:加密是 y = f(x),解密就是 x = f(y),加密和解密是同一个,靠这一个算法就可以完成加密解密
    • 非对称密码算法:加密是 y = f(x),解密就是 x = m(y),加密和解密是不同的
      • 关于公钥和私钥:公钥类似于锁,通常存储在服务器上。私钥类似于钥匙,通常存储在客户端,一个锁会有很多把钥匙,钥匙可以不同。所以通常情况下,公钥是公开的,大家都可以拿到,但是没有正确

        一个文件通过锁进行了加密,别人是无法直接查看该文件的内容的,钥匙是在用户手中,且可以不一样,如小红和小王都拿了把钥匙,钥匙可以不同,但都能打开这把锁。

      • 关于安全性:没有非对称加密比对称加密更安全的说法,两者都是安全的,取决于实现方式

    • 摘要算法:不是字符串的编码方式,如果两个字符串加密后的密文一样,我们就认为明文是一样的
  3. Https用了对称加密和非对称加密两种手段
  4. 关于摘要算法之MD5加密

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

  1. 使用MD5加密解密
    • DigestUtils:import org.springframework.util.DigestUtils Spring中用来实现加密的一个工具
      在这里插入图片描述

六、部署服务

6.1 部署的流程

  1. 原理:把项目打包成一个Jar包,让它在远程的云服务器上运行
  2. 流程
    • 确保当前能运行Java程序:有jdk和mysql
    • 修改配置文件:修改 application.yml 配置文件,如修改数据库账号密码和日志保存位置
      • 使用多平台配置:不需要我们来回改application.yml,而是能根据【不同的环境/平台】去采用不同的配置文件
        • 不同的环境:当前是【开发环境】,部署到服务器上后为【线上环境/生产环境】,不同的环境上需要的配置是不同的,比如【开发环境】里需要打印SQL,但到【线上环境】后,就不需要打印了
    1. 打Jar包:IDEA上面可以用Maven来帮忙打Jar包
    2. 在Linux服务器上运行该项目

6.2 搭建Java部署环境

安装jdk

  1. centos 环境下
    在这里插入图片描述
  2. unbuntu 环境下
    在这里插入图片描述

安装mysql

  1. unbuntu 环境下安装

在这里插入图片描述

  1. mariaDB VS mysql
    • 相同点:两者相似,端口都是3306
    • 不同点:mariaDB会影响建表时间 以及 不支持数据库的DateTime类型
      在这里插入图片描述

6.3 多平台配置

  1. 使用场景:让每个平台都有自己专属的配置文件,省得我们在一个application.yml里改来改去
  2. 如何使用多平台配置
    在这里插入图片描述

6.4 部署

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/908140.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

普林斯顿微积分读本PDF(英文版原版)

普林斯顿微积分读本PDF英文版: https://caiyun.139.com/m/i?005Chb93yVHtQ 对于大多数学生来说&#xff0c;微积分或许是他们曾经上过的倍感迷茫且最受挫折的一门课程了. 而《普林斯顿微积分读本》 不仅让学生能有效地学习微积分&#xff0c;更重要的是提供了战胜微积分的必备…

OpenCV视觉分析之目标跟踪(8)目标跟踪函数CamShift()使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 找到物体的中心、大小和方向。 CamShift&#xff08;Continuously Adaptive Mean Shift&#xff09;是 OpenCV 中的一种目标跟踪算法&#xff0…

【开源免费】基于SpringBoot+Vue.J服装商城系统(JAVA毕业设计)

本文项目编号 T 046 &#xff0c;文末自助获取源码 \color{red}{T046&#xff0c;文末自助获取源码} T046&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 新…

叉车智能管理系统,简化现场管理!

一、叉车智能管理系统概述 叉车智能管理系统是一种集成了现代物联网、大数据、云计算等先进技术的综合性管理平台。它通过对叉车运行状态的实时监控和数据分析&#xff0c;实现了对叉车作业的高效调度和智能化管理&#xff0c;极大地提升了企业的现场作业效率和管理水平。 二…

【MySQL】 穿透学习数据库理论与知识剖析

前言&#xff1a;本节内容讲述一些数据库的基本概念。 第一个部分就是数据库相关的概念&#xff0c; 比如什么是数据库&#xff0c; 如何理解mysqld以及mysql。第二部分理解数据库和表在系统层面的形式。 第三部分就是mysql的一些操作分类。 第四部分就是数据库的插件配置这些。…

Mysql常用语法一篇文章速成

文章目录 前言前置环境数据库的增删改查查询数据查询所有条件查询多条件查询模糊查询分页查询排序查询分组查询⭐️⭐️关联查询关联分页查询 添加数据insert插入多条记录不指定列名(适用于所有列都有值的情况) 更新数据更新多条记录更新多个列更新不满足条件的记录 删除统计数…

论文阅读-用于图像识别的深度残差学习

目前存在的问题&#xff1a; 文章主要研究内容&#xff1a; 未来课研究内容&#xff1a; 涉及的理论问zzz 在这种 论文研究思路 论文翻译&#xff1a; 摘要&#xff1a;更深层次的神经网络更难训练。 我们提出了一个残差学习框架&#xff0c;以简化比以前使用的网络更深的网络训…

AD 授予委派full control权限后,部分用户无法unlock

文章目录 问题描叙根因解決方法&#xff1a; 问题描叙 通過委派方式授予被委派用戶full control 权限后&#xff0c;部分用户unlock是灰色显示&#xff1a; 根因 对比能正常unlock与无法unlock的用户&#xff0c;发现繼承無法unlock的用戶沒有"enable inheritance&quo…

Maven随笔

文章目录 1、什么是MAVEN2、Maven模型3、Maven仓库4、项目集成1_Idea集成Maven设置2_创建Maven项目3_POM配置详解4_maven 坐标详情5_Maven工程类型6_导入Maven项目 5、依赖管理1_依赖配置2_依赖传递3_可选依赖4_排除依赖4_可选依赖和排除依赖的区别5_依赖范围6_继承与聚合7_版本…

Vue中ref、reactive、toRef、toRefs的区别

一、ref、reactive setup 函数中默认定义的变量并不是响应式的&#xff08;即数据变了以后页面不会跟着变&#xff09;&#xff0c;如果想让变量变为响应式的变量&#xff0c;需要使用 ref 和 reactive 函数修饰变量。 ref 函数可以把基本类型变量变为响应式引用reactive 函数…

低压补偿控制器维修措施

低压补偿控制器其主要功能是调节和补偿系统中的低压&#xff0c;确保设备的正常运行和电能的高效利用。然而&#xff0c;随着长时间的使用&#xff0c;低压补偿控制器可能会出现故障&#xff0c;从而影响系统的正常运行。因此&#xff0c;了解低压补偿控制器的维修措施显得尤为…

视频推荐的算法(字节青训)

题目&#xff1a; 西瓜视频 正在开发一个新功能&#xff0c;旨在将访问量达到80百分位数以上的视频展示在首页的推荐列表中。实现一个程序&#xff0c;计算给定数据中的80百分位数。 例如&#xff1a;假设有一个包含从1到100的整数数组&#xff0c;80百分位数的值为80&#…

lora训练模型 打造个人IP

准备工作 下载秋叶炼丹器整理自己的照片下载底膜 https://rentry.org/lycoris-experiments 实操步骤 解压整合包 lora-scripts,先点击“更新” 训练图片收集 比如要训练一个自己头像的模型&#xff0c;就可以拍一些自己的照片&#xff08;20-50张&#xff0c;最少15张&…

qt QBrush详解

1、概述 QBrush是Qt框架中的一个基本图形对象类&#xff0c;它主要用于定义图形的填充模式。QBrush可以用于填充如矩形、椭圆形、多边形等形状&#xff0c;也可以用于绘制背景等。通过QBrush&#xff0c;可以设置填充的颜色、样式&#xff08;如实心、渐变、纹理等&#xff09…

《C#语法一篇通》,20万字,48小时阅读,持续完善中。。。

本文摘录了C#语法的主要内容&#xff0c;接近20万字。 所有鸡汤的味道都等于马尿&#xff01; 如果你相信任何所谓的鸡汤文章&#xff0c;智商堪忧。 计算机语言没有”好不好“之说&#xff0c;骗子才会告诉你哪个语言好&#xff0c;学好任何一本基础语言&#xff08;C&#…

radio astronomy 2

地球上的电离层会被太阳风影响。

数字人直播带货前景如何?头部源码厂商的系统能实现哪些功能?

随着数字人直播技术的成熟&#xff0c;以数字人直播带货为代表的应用场景逐渐呈现出常态化的趋势&#xff0c;使得越来越多创业者对该赛道产生兴趣的同时&#xff0c;也让数字人直播带货前景及操作方式成为了他们所重点关注的对象。 从目前的情况来看&#xff0c;就数字人直播带…

华为鲲鹏一体机 安装笔记

安装驱动 在这个链接 社区版-固件与驱动-昇腾社区 1 下载NPU固件 需要注册登录&#xff0c;否则报错&#xff1a; ERR_NO:0x0091;ERR_DES:HwHiAiUser not exists! Please add HwHi AiUser 准备软件包-软件安装-CANN…

【C++】类和对象(十一):友元+内部类+匿名函数

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家了解C的友元内部类匿名函数&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 1. 友元1.1 友元函数1.2 友元类 2. 内部类3. 匿名对象 1. 友元 友元提供了一种…

【深度学习】VITS语音合成原理解析

1、前言 呃。。。好多天没更新了&#xff0c;最近 黑神话悟空 相当火啊&#xff0c;看上瘾了。本篇内容&#xff0c;我们来讲一下VITS。 视频&#xff1a;语言合成 & 变声器 ——VITS原理解析①_哔哩哔哩_bilibili 2、VITS 训练图 预测图&#xff1a; 2.1、条件VAE的优…