2024/9/3黑马头条跟学笔记(一)

D1

此版本忘记设置md格式了,介意的话看主页的下一篇文章

视频链接

Day1-05-nacos环境搭建_哔哩哔哩_bilibili

内容介绍

  1. 搭建微服务开发环境,登录接口包含注册中心和nacos配置中心

    服务端用户…微服务。网关负载均衡转发接口请求

    实现微服务间互相通信

  2. 接口测试

  3. 前后端联调

image-20240902072116857

前置知识

image-20240902073023383

背景介绍

image-20240902073303445

类似今日头条,qq看点

功能架构图


用户前台,内容浏览,登录注册。。。类似于看小说的人

用户后台,内容发布,私信管理。。。类似于小说家

超级管理员后台,对所有作者,其发布内容管理,类似于上帝

效果预览

image-20240902073958133

用户群众

image-20240902073903422

用户,吃瓜的——app用户端

自媒体人,卖瓜的——自媒体系统

管理员,城管——admin管理系统

技术选型

image-20240902074157948

image-20240902074217155

基础层负责前端nginx转发和cdn资源存储加快访问速度,前端框架组件及其第三方插件echarts快速开发

服务层也就是后端首先cloudGateway网关转发请求到各个服务接口,例如登录接口。。

同时还有数据层,持久化数据,mysql。mongodb。hbase。

中间件kafka,redis缓存

搜索索引elasticsearch,oss对象存储

收获

image-20240902074808270

技术运用

解决方案,分布式事务,任务调度,延迟队列,异步线程,热数据,评论系统,关注点赞

软硬编程思想,数据库设计,代码编写,测试用例,部署

课程大纲

image-20240902075030288

环境搭建

列表查看

热点计算(大数据)

cms前后台管理系统发布和审核(队列)

部署,迁移数据

实战(app端文章行为,评论系统,自媒体端评论管理,报表)

环境搭建

nacos环境搭建

  1. 安装一台虚拟机centos

  2. docker安装nacos

镜像拉取

docker pull nacos/nacos-server:1.2.0

创建容器

docker run --env MODE=standalone --name nacos --restart=always  -d -p 8848:8848 nacos/nacos-server:1.2.0

//以下是国内无法拉取镜像的解决方案,具体请看往期文章
docker run --env MODE=standalone --name nacos --restart=always  -d -p 8848:8848 registry.cn-heyuan.aliyuncs.com/zwww/nacos-server:1.2.0

单机模式,名字,开机重启,守护式进程,端口号,容器镜像

访问地址 192.168.233.136:8848/nacos

image-20240902103007010

初始工程搭建

image-20240902103138943

maven仓库我是用的之前自己配置的

文件编码全设置为utf-8

image-20240902104002448

多模块开发——模块讲解

image-20240902104343937

common——所有服务都用到的,比如全局异常处理

feign-api——对外接口类似controller

gateway——网关转发

model——实体类

service——微服务

test——测试用例

utils——全局工具包

异常类

分为两种,

一种是已知的异常,如参数缺失,返回值自行DIY,

一种是未知异常系统异常,返回值固定

是的,你的理解是正确的。@Controller@ControllerAdvice 之间的工作流程可以总结如下:@ControllerAdvice会将异常丢给前端

需要(自定义异常实体类,异常捕获且进行返回或是log.info打印的类)

工作流程

  1. 请求接收
  • 控制器(使用 @Controller 注解的类)负责接收 HTTP 请求并处理业务逻辑。
  1. 异常抛出
  • 在处理请求的过程中,如果发生异常(如自定义异常或系统异常),控制器会抛出该异常。
  1. 异常捕获
  • 被抛出的异常会被 Spring 的异常处理机制捕获。如果该异常没有在控制器内部处理,它会被匹配到对应的 @ExceptionHandler 方法。
  • 这里,@ControllerAdvice 注解的类会起作用,检查是否有适用的异常处理方法。
  1. 返回响应
  • @ExceptionHandler 方法中,你可以定义如何处理异常,并返回一个响应实体(例如 ResponseEntity)。
  • 这个响应会作为控制器的返回值返回给前端,包含相应的状态码和错误信息。

初始化文件

image-20240902105431385

当微服务引入了common依赖,当微服务初始容器时找到该文件并进行初始化,此时微服务可以使用该全局异常处理器

APP登录

需求分析

image-20240902105723646

登陆前只能看

登陆后能点赞评论关注

表结构

导入sql脚本

CREATE TABLE `ap_user` (
  `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',
  `salt` VARCHAR(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '密码、通信等加密盐',
  `name` VARCHAR(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '用户名',
  `password` VARCHAR(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '密码,md5加密',
  `phone` VARCHAR(11) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '手机号',
  `image` VARCHAR(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '头像',
  `sex` TINYINT(1) UNSIGNED DEFAULT NULL COMMENT '0 男\r\n            1 女\r\n            2 未知',
  `is_certification` TINYINT(1) UNSIGNED DEFAULT NULL COMMENT '0 未\r\n            1 是',
  `is_identity_authentication` TINYINT(1) DEFAULT NULL COMMENT '是否身份认证',
  `status` TINYINT(1) UNSIGNED DEFAULT NULL COMMENT '0正常\r\n            1锁定',
  `flag` TINYINT(1) UNSIGNED DEFAULT NULL COMMENT '0 普通用户\r\n            1 自媒体人\r\n            2 大V',
  `created_time` DATETIME DEFAULT NULL COMMENT '注册时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=INNODB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='APP用户信息表';

image-20240902113942940

实体类
package com.heima.model.user.pojos;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

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

/**
 * <p>
 * APP用户信息表
 * </p>
 *
 * @author itheima
 */
@Data
@TableName("ap_user")
public class ApUser implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 主键
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 密码、通信等加密盐
     */
    @TableField("salt")
    private String salt;

    /**
     * 用户名
     */
    @TableField("name")
    private String name;

    /**
     * 密码,md5加密
     */
    @TableField("password")
    private String password;

    /**
     * 手机号
     */
    @TableField("phone")
    private String phone;

    /**
     * 头像
     */
    @TableField("image")
    private String image;

    /**
     * 0 男
            1 女
            2 未知
     */
    @TableField("sex")
    private Boolean sex;

    /**
     * 0 未
            1 是
     */
    @TableField("is_certification")
    private Boolean certification;

    /**
     * 是否身份认证
     */
    @TableField("is_identity_authentication")
    private Boolean identityAuthentication;

    /**
     * 0正常
            1锁定
     */
    @TableField("status")
    private Boolean status;

    /**
     * 0 普通用户
            1 自媒体人
            2 大V
     */
    @TableField("flag")
    private Short flag;

    /**
     * 注册时间
     */
    @TableField("created_time")
    private Date createdTime;

}
手动加密(md5+加盐随机字符串)

image-20240902113020737

md5每次加密后都是一样的 会被别人试出来正确密码,因此我们每次MD5加生成一个随机字符串一起加密,这样即使是相同密码的md5加密出来的值也是不一样的

流程为,登陆时将输入的密码和盐字符串进行加密 比对数据库一开始加密后的密码

用户端微服务搭建

service模块

image-20240902114556089

 <dependencies>
        <!-- 引入依赖模块 -->
        <dependency>
            <groupId>com.heima</groupId>
            <artifactId>heima-leadnews-model</artifactId>
        </dependency>
        <dependency>
            <groupId>com.heima</groupId>
            <artifactId>heima-leadnews-common</artifactId>
        </dependency>
        <dependency>
            <groupId>com.heima</groupId>
            <artifactId>heima-leadnews-feign-api</artifactId>
        </dependency>
        <!-- Spring boot starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
    </dependencies>

新建用户子模块

image-20240902114757319

新建引导类

先搞个user包出来,在user包下粘贴以下代码

package com.heima.user;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.heima.user.mapper")
public class UserApplication {

    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class,args);
    }
}

文件+包初始化

image-20240902115556148

bootstrap.yml

引导配置文件,相当于application.yml

ip改成你虚拟机的地址

server:
  port: 51801
spring:
  application:
    name: leadnews-user
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.233.136:8848
      config:
        server-addr: 192.168.233.136:8848
        file-extension: yml
在nacos中创建配置文件

image-20240902115857428

image-20240902120150875

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/leadnews_user?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: 123456
# 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置
mybatis-plus:
  mapper-locations: classpath*:mapper/*.xml
  # 设置别名包扫描路径,通过该属性可以给包中的类注册别名
  type-aliases-package: com.heima.model.user.pojos

这里的mybatis依赖在model的pom里引入了

logback.xml
<?xml version="1.0" encoding="UTF-8"?>

<configuration>
    <!--定义日志文件的存储地址,使用绝对路径-->
    <property name="LOG_HOME" value="e:/logs"/>

    <!-- Console 输出设置 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>

    <!-- 按照每天生成日志文件 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名-->
            <fileNamePattern>${LOG_HOME}/leadnews.%d{yyyy-MM-dd}.log</fileNamePattern>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 异步输出 -->
    <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
        <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
        <discardingThreshold>0</discardingThreshold>
        <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
        <queueSize>512</queueSize>
        <!-- 添加附加的appender,最多只能添加一个 -->
        <appender-ref ref="FILE"/>
    </appender>


    <logger name="org.apache.ibatis.cache.decorators.LoggingCache" level="DEBUG" additivity="false">
        <appender-ref ref="CONSOLE"/>
    </logger>
    <logger name="org.springframework.boot" level="debug"/>
    <root level="info">
        <!--<appender-ref ref="ASYNC"/>-->
        <appender-ref ref="FILE"/>
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration>

image-20240902120042688

日志输出位置

image-20240902120237802

更加详细的日志信息

总体结构

image-20240902120402052

接口定义

image-20240902132017905

httpenum枚举

以后直接复制粘贴,不用设置常量了

package com.heima.model.common.enums;

public enum AppHttpCodeEnum {

    // 成功段0
    SUCCESS(200,"操作成功"),
    // 登录段1~50
    NEED_LOGIN(1,"需要登录后操作"),
    LOGIN_PASSWORD_ERROR(2,"密码错误"),
    // TOKEN50~100
    TOKEN_INVALID(50,"无效的TOKEN"),
    TOKEN_EXPIRE(51,"TOKEN已过期"),
    TOKEN_REQUIRE(52,"TOKEN是必须的"),
    // SIGN验签 100~120
    SIGN_INVALID(100,"无效的SIGN"),
    SIG_TIMEOUT(101,"SIGN已过期"),
    // 参数错误 500~1000
    PARAM_REQUIRE(500,"缺少参数"),
    PARAM_INVALID(501,"无效参数"),
    PARAM_IMAGE_FORMAT_ERROR(502,"图片格式有误"),
    SERVER_ERROR(503,"服务器内部错误"),
    // 数据错误 1000~2000
    DATA_EXIST(1000,"数据已经存在"),
    AP_USER_DATA_NOT_EXIST(1001,"ApUser数据不存在"),
    DATA_NOT_EXIST(1002,"数据不存在"),
    // 数据错误 3000~3500
    NO_OPERATOR_AUTH(3000,"无权限操作"),
    NEED_ADMIND(3001,"需要管理员权限");

    int code;
    String errorMessage;

    AppHttpCodeEnum(int code, String errorMessage){
        this.code = code;
        this.errorMessage = errorMessage;
    }

    public int getCode() {
        return code;
    }

    public String getErrorMessage() {
        return errorMessage;
    }
}

静态方法获取枚举对象,包含code和message

image-20240902130023141

响应对象静态方法调用成功标识,获取枚举并填入

image-20240902130253118

image-20240902131233974

也可以自定义提示信息,传enum和自定义message

image-20240902131336496

分页信息结果类

继承了基础的结果类,构造函数填入 pageNum,pageSize,total

该类在构造时如果没有super()构造父类则默认无参构造父类

image-20240902131923986

image-20240902131928609

功能实现

思路分析

思路

image-20240902114335963

相关类

ApUserLoginController接口类
package com.heima.user.configcontroller.v1;

import com.heima.model.common.dtos.ResponseResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/v1/login")
public class ApUserLoginController {

    @PostMapping("/login_auth")
    public ResponseResult login(@RequestBody LoginDto dto) {
        return null;
    }
}

logindto

dto前端传来的数据,vo,后端给前端的数据

package com.heima.model.user.dtos;


import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

@Data
public class LoginDto {

    /**
     * 手机号
     */
    @ApiModelProperty(value = "手机号",required = true)
    private String phone;

    /**
     * 密码
     */
    @ApiModelProperty(value = "密码",required = true)
    private String password;
}

mapper
package com.heima.user.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.heima.model.user.pojos.ApUser;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface ApUserMapper extends BaseMapper<ApUser> {
}

业务层service
package com.heima.user.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.user.dtos.LoginDto;
import com.heima.model.user.pojos.ApUser;

public interface ApUserService extends IService<ApUser>{

    /**
     * app端登录
     * @param dto
     * @return
     */
    public ResponseResult login(LoginDto dto);
    
}

业务实现类ApUserServiceImpl
package com.heima.user.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.user.dtos.LoginDto;
import com.heima.model.user.pojos.ApUser;
import com.heima.user.mapper.ApUserMapper;
import com.heima.user.service.ApUserService;
import com.heima.utils.common.AppJwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.cli.Digest;
import org.apache.commons.lang3.StringUtils;
import org.checkerframework.checker.units.qual.A;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;

import java.util.HashMap;
import java.util.Objects;


@Service
@Transactional
@Slf4j
public class ApUserServiceImpl extends ServiceImpl<ApUserMapper, ApUser> implements ApUserService {

    @Autowired
    private ApUserMapper apUserMapper;

    /**
     * app端登录功能
     *
     * @param dto
     * @return
     */
    @Override
    public ResponseResult login(LoginDto dto) {
        // 用户登录,有传递用户名和密码就不是游客
        if (StringUtils.isNotBlank(dto.getPhone()) && StringUtils.isNotBlank(dto.getPassword())) {
            // 拿用户名查数据库,如果是空返回用户不存在响应类
            ApUser dbUser = apUserMapper.selectById(dto.getPhone());
            if (Objects.isNull(dbUser)) {
                return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST, "用户不存在");
            }
            // 如果用户存在且输入了密码,获取盐值,和用户输入的密码一同进行加密,如果匹配成功则以id返回token
            String salt = dbUser.getSalt();
            String password = dto.getPassword();
            String pswd = DigestUtils.md5DigestAsHex((password + salt).getBytes());
            if (!pswd.equals(password)) {
                return ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_PASSWORD_ERROR);
            }
            String token = AppJwtUtil.getToken(dbUser.getId().longValue());
            HashMap<String, Object> stringObjectHashMap = new HashMap<>();
            stringObjectHashMap.put("token", token);
            dbUser.setPassword("");
            dbUser.setSalt("");
            stringObjectHashMap.put("user", dbUser);

            return ResponseResult.okResult(stringObjectHashMap);
        } else {
            // 如果没输入密码,以0生成token,返回前端,视为游客
            HashMap<String, Object> map = new HashMap<>();
            map.put("token", AppJwtUtil.getToken(0L));
            return ResponseResult.okResult(map);
        }
    }
}

启动测试

接口测试工具

postman

http://localhost:51801/api/v1/login/login_auth

这里sql脚本的密码不与视频中admin一致,前往测试类生成一个复制到数据库里再次登录即可

image-20240902224501507

swagger接口文档

  • 在线的,发生变化实时更新

  • 可进行功能测试

依赖引入到model和common

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
</dependency>

增加自动配置类SwaggerConfiguration

package com.heima.common.swagger;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfiguration {

   @Bean
   public Docket buildDocket() {
      return new Docket(DocumentationType.SWAGGER_2)
              .apiInfo(buildApiInfo())
              .select()
              // 要扫描的API(Controller)基础包
              .apis(RequestHandlerSelectors.basePackage("com.heima"))
              .paths(PathSelectors.any())
              .build();
   }

   private ApiInfo buildApiInfo() {
      Contact contact = new Contact("黑马程序员","","");
      return new ApiInfoBuilder()
              .title("黑马头条-平台管理API文档")
              .description("黑马头条后台api")
              .contact(contact)
              .version("1.0.0").build();
   }
}

扫描heima包下的所有类,进行接口文档配置

配置到factory清单文件里,当common加载时加载清单里的类

spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.heima.common.exception.ExceptionCatch
com.heima.common.swagger.SwaggerConfiguration
com.heima.common.swagger.Swagger2Configuration

注解

常见对应关系如下

@Api = controller整个的作用

@ApiOperation = controller下的方法或者接口

@ApiParam = 接口方法参数信息

@ApiModel = 对象

@ApiModel = 实体类

@@ApiModelProperty = 实体类属性

其他注解

@ApiResponse:HTTP响应其中1个描述

@ApiResponses:HTTP响应整体描述

@ApiIgnore:使用该注解忽略这个API

@ApiError :发生错误返回的信息

@ApiImplicitParam:一个请求参数

@ApiImplicitParams:多个请求参数的描述信息

启动user微服务,访问地址:http://localhost:51801/swagger-ui.html

测试成功

image-20240903084819347

knife4J

image-20240903084954355

提供离线文档

依赖引入

common模块下

<dependency>
     <groupId>com.github.xiaoymin</groupId>
     <artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>

配置类

package com.heima.common.swagger;

import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
@EnableKnife4j
@Import(BeanValidatorPluginsConfiguration.class)
public class Swagger2Configuration {

    @Bean(value = "defaultApi2")
    public Docket defaultApi2() {
        Docket docket=new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                //分组名称
                .groupName("1.0")
                .select()
                //这里指定Controller扫描包路径
                .apis(RequestHandlerSelectors.basePackage("com.heima"))
                .paths(PathSelectors.any())
                .build();
        return docket;
    }
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("黑马头条API文档")
                .description("黑马头条API文档")
                .version("1.0")
                .build();
    }
}

bean扫描清单

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.heima.common.exception.ExceptionCatch,\
  com.heima.common.swagger.SwaggerConfiguration,\
  com.heima.common.swagger.Swagger2Configuration

访问

http://localhost:51801/doc.html

image-20240903094629369 image-20240903094812475 image-20240903094840436

APP网关

image-20240903094935502 image-20240903095002773

管理员/自媒体/app网关

依赖引入

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
     <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
    </dependency>
</dependencies>

包含了管理所有模块的网关依赖,nacos服务发现与注册,jwt依赖

新建app-gateway模块指定父模块

image-20240903095744809

启动类

image-20240903095457195

package com.heima.app.gateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;


@SpringBootApplication
@EnableDiscoveryClient
public class AppGatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(AppGatewayApplication.class,args);
    }
}

@EnableDiscoveryClient //开启注册中心

配置文件bootstrap.yml

server:
  port: 51601
spring:
  application:
    name: leadnews-app-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.233.136:8848
      config:
        server-addr: 192.168.233.136:8848
        file-extension: yml

注册到nacos

image-20240903095945759
spring:
  cloud:
    gateway:
      globalcors:
        add-to-simple-url-handler-mapping: true
        corsConfigurations:
          '[/**]':
            allowedHeaders: "*"
            allowedOrigins: "*"
            allowedMethods:
              - GET
              - POST
              - DELETE
              - PUT
              - OPTION
      routes:
        # 平台管理
        - id: user
          uri: lb://leadnews-user
          predicates:
            - Path=/user/**
          filters:
            - StripPrefix= 1

启动并请求测试

http://localhost:51601/user/api/v1/login/login_auth

image-20240903100831805

认证过滤器jwt

思路分析

image-20240903101034191

登录请求不鉴权,直接去微服务校验密码用户名

其他先判断token,在判断有效期

有效就放行到想去的微服务上

步骤

实现过滤器类,后续请求会被该过滤器拦截

JWT工具类

package com.heima.app.gateway.util;

import io.jsonwebtoken.*;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.*;

public class AppJwtUtil {

    // TOKEN的有效期一天(S)
    private static final int TOKEN_TIME_OUT = 3_600;
    // 加密KEY
    private static final String TOKEN_ENCRY_KEY = "MDk4ZjZiY2Q0NjIxZDM3M2NhZGU0ZTgzMjYyN2I0ZjY";
    // 最小刷新间隔(S)
    private static final int REFRESH_TIME = 300;

    // 生产ID
    public static String getToken(Long id){
        Map<String, Object> claimMaps = new HashMap<>();
        claimMaps.put("id",id);
        long currentTime = System.currentTimeMillis();
        return Jwts.builder()
                .setId(UUID.randomUUID().toString())
                .setIssuedAt(new Date(currentTime))  //签发时间
                .setSubject("system")  //说明
                .setIssuer("heima") //签发者信息
                .setAudience("app")  //接收用户
                .compressWith(CompressionCodecs.GZIP)  //数据压缩方式
                .signWith(SignatureAlgorithm.HS512, generalKey()) //加密方式
                .setExpiration(new Date(currentTime + TOKEN_TIME_OUT * 1000))  //过期时间戳
                .addClaims(claimMaps) //cla信息
                .compact();
    }

    /**
     * 获取token中的claims信息
     *
     * @param token
     * @return
     */
    private static Jws<Claims> getJws(String token) {
            return Jwts.parser()
                    .setSigningKey(generalKey())
                    .parseClaimsJws(token);
    }

    /**
     * 获取payload body信息
     *
     * @param token
     * @return
     */
    public static Claims getClaimsBody(String token) {
        try {
            return getJws(token).getBody();
        }catch (ExpiredJwtException e){
            return null;
        }
    }

    /**
     * 获取hearder body信息
     *
     * @param token
     * @return
     */
    public static JwsHeader getHeaderBody(String token) {
        return getJws(token).getHeader();
    }

    /**
     * 是否过期
     *
     * @param claims
     * @return -1:有效,0:有效,1:过期,2:过期
     */
    public static int verifyToken(Claims claims) {
        if(claims==null){
            return 1;
        }
        try {
            claims.getExpiration()
                    .before(new Date());
            // 需要自动刷新TOKEN
            if((claims.getExpiration().getTime()-System.currentTimeMillis())>REFRESH_TIME*1000){
                return -1;
            }else {
                return 0;
            }
        } catch (ExpiredJwtException ex) {
            return 1;
        }catch (Exception e){
            return 2;
        }
    }

    /**
     * 由字符串生成加密key
     *
     * @return
     */
    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.getEncoder().encode(TOKEN_ENCRY_KEY.getBytes());
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }

    public static void main(String[] args) {
       /* Map map = new HashMap();
        map.put("id","11");*/
        System.out.println(AppJwtUtil.getToken(1102L));
        Jws<Claims> jws = AppJwtUtil.getJws("eyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAADWLQQqEMAwA_5KzhURNt_qb1KZYQSi0wi6Lf9942NsMw3zh6AVW2DYmDGl2WabkZgreCaM6VXzhFBfJMcMARTqsxIG9Z888QLui3e3Tup5Pb81013KKmVzJTGo11nf9n8v4nMUaEY73DzTabjmDAAAA.4SuqQ42IGqCgBai6qd4RaVpVxTlZIWC826QA9kLvt9d-yVUw82gU47HDaSfOzgAcloZedYNNpUcd18Ne8vvjQA");
        Claims claims = jws.getBody();
        System.out.println(claims.get("id"));

    }

}

过滤器类

package com.heima.app.gateway.filter;


import com.heima.app.gateway.util.AppJwtUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.URI;

@Component
@Slf4j
public class AuthorizeFilter implements Ordered, GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.获取request和response对象
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        // 1,获取请求路径,如果是登录请求则放行
        URI uri = request.getURI();
        if (uri.getPath().contains("/login")) {
            return chain.filter(exchange);
        }
        // 2,如果不是登录请求,获取token,判断是否存在
        String token = exchange.getRequest().getHeaders().getFirst("token");
        if (StringUtils.isEmpty(token)) {
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }
        // 解析token可能会失败因此我们用try catch将其包住在catch处也返回401
        // 3,如果存在token,那么再判断其有效期
        try {
            Claims claimsBody = AppJwtUtil.getClaimsBody(token);
            //验证是否过期
            int verifyToken = AppJwtUtil.verifyToken(claimsBody);
            if(verifyToken==1||verifyToken==2){
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                return response.setComplete();
            }
        } catch (Exception e) {
            e.printStackTrace();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }
        // 4,如果token在有效期内则放行
        return chain.filter(exchange);
    }

    /**
     * 优先级设置  值越小  优先级越高
     *
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

App端前端项目集成

image-20240903102609298

解压nginx压缩包(路径必须全英文),web项目,配置nginx.conf

先配置nginx.conf其端口为8222,因为80一般早被占用了

image-20240903103549543

不搞到linux上了,直接在window用cmd打开根目录后输入nginx进行启动

image-20240903103727774

http://localhost:8222/

image-20240903103801825

配置nginx服务器地址跳转到前端项目

由于一共有三个前端服务,我们给他们分别创建一个配置文件,后续用包含的方式将他们归结在一起,类似于html=》vue的过程

在conf文件夹中新建一个leadnews.conf文件夹

新建第一个配置文件heima-leadnews-app.conf内容如下(注意,有些解压可能会给你多嵌套一层app-web。)

upstream  heima-app-gateway{
    server localhost:51601;
}

server {
	listen 8801;
	location / {
		root  你前端项目的地址;
		index index.html;
	}
	
	location ~/app/(.*) {
		proxy_pass http://heima-app-gateway/$1;
		proxy_set_header HOST $host;  # 不改变源请求头的值
		proxy_pass_request_body on;  #开启获取请求体
		proxy_pass_request_headers on;  #开启获取请求头
		proxy_set_header X-Real-IP $remote_addr;   # 记录真实发出请求的客户端IP
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;  #记录代理信息
	}
}

在nginx。conf引入所有子配置文件,内容如下,直接覆盖原先所有内容

#user  nobody;
worker_processes  1;

events {
    worker_connections  1024;
}
http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
	# 引入自定义配置文件
	include leadnews.conf/*.conf;
}

重载nginx,生效配置文件,在刚才打开cmd启动nginx那个目录上输入

nginx.exe -s reload
image-20240903104918152

登录成功

image-20240903110029748

游客登录则默认token

image-20240903110113444
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值