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

news2024/11/25 20:49:19

D1

视频链接

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

权威解读:社交类APP都需要办理哪些资质?

今天小编给大家讲讲社交类APP都需要办理哪些资质&#xff1f; 我们先来看下微信小程序对社交类目是怎么分类以及需要哪些资质许可证&#xff1f; 微信小程序社交类目许可资质 微信小程序对社交类目做了一些细分&#xff0c;它把社交分为陌生人交友、熟人交友、社区/论坛、直播…

log4j 控制台和文件输出乱码问题解决

一个小问题&#xff0c;却让我感觉到&#xff0c;现在真正动脑的人很少。。我来说说吧。 今天遇到一个小问题&#xff0c; log4j输出到文件乱码&#xff0c;控制台正常。显然是编码问题导致。Google一搜&#xff0c;几乎一水的说&#xff1a; 项目中log4j在英文版linux下输出中…

气膜水产养殖:打造高效、可持续的水产养殖新模式—轻空间

随着全球对高质量水产品需求的不断增加&#xff0c;传统的水产养殖方式面临着诸多挑战&#xff0c;如环境污染、气候变化以及水源短缺等问题。在这种背景下&#xff0c;气膜水产养殖作为一种创新的养殖模式&#xff0c;逐渐引起了广泛关注。通过结合气膜结构建筑与现代化养殖技…

【测试】系统测试用例编写案例模板(Word原件)

1编写目的 2使用范围 3文档概述 4术语和缩略语 5编写规范 5.1编写目的 5.2编写范围 5.3编写规范 6参考文档 软件全套精华资料包清单部分文件列表&#xff1a; 工作安排任务书&#xff0c;可行性分析报告&#xff0c;立项申请审批表&#xff0c;产品需求规格说明书&#xff0c;需…

从UGC到PGC:3C品牌与TikTok达人合作的内容优化策略

在数字营销新时代&#xff0c;内容的创作和传播方式正在经历快速的变革。3C品牌与TikTok达人的合作正逐渐从用户生成内容&#xff08;UGC&#xff09;向专业生成内容&#xff08;PGC&#xff09;转变。这一转变不仅改变了内容的生产方式&#xff0c;也提升了品牌营销的效果。本…

三种权限模型该如何选择

在构建企业级平台或复杂应用系统时&#xff0c;权限管理是一个至关重要的环节。它决定了哪些用户可以访问哪些资源&#xff0c;以及可以进行哪些操作&#xff0c;一个健全的权限管理架构&#xff0c;在确保系统正常运行的同时&#xff0c;也能有效防止数据泄露和非法访问&#…

antd:手写走马灯vue组件

在使用ant-design-vue做走马灯的时候,封装的组件的自由度太低,难以实现想要的效果,于是本人自己写了一个走马灯组件,以方便代码复用。本文将介绍如何在vue框架中,使用ant-design-vue手动实现走马灯组件效果。 结果如下图所示, 一、使用说明 使用时,直接创建一个组件,…

.NET 最好用的验证组件 FluentValidation

目录 前言 项目介绍 项目使用 1、安装FluentValidation 2、Program.cs 3、Startup.cs 4、版本兼容 5、支持的验证器 6、可扩展 7、Swagger 模型和验证器 8、包含验证器 高级用法 1、异步验证 2、条件验证 3、自定义验证规则 4、自定义错误消息 项目地址 总结 …

comfyui替换电商模特工作流,模特们要真的要失业了吗?

前言 comfyui生态的丰富绝对是电商行业的福利&#xff0c;有助于电商老板们开源节流&#xff0c;废话不多说本着追求进步进一步理解comfyui工作流的搭建逻辑&#xff0c;我们来拆解电商模特替换这个工作流&#xff01; 老规矩一句话说工作流原理&#xff0c;1.借助XL-tile修改…

Funsound: 快速为你的视频加上字幕

Funsound是基于阿里达摩院funasr开发的中文语音识别工具&#xff0c;其paraformer非自回归解码速度超快&#xff0c;同时预训练模型识别精度业界领先。本文将简要介绍funsound下如何快速为你的视频添加字幕&#xff0c;十分简单方便。 1. 上传音视频识别 & 导出SRT 打开fu…

无人机之飞行速度篇

无人机的飞行速度是一个复杂且多变的参数&#xff0c;它受到多种因素的影响。以下是对无人机飞行速度及其影响因素的详细分析&#xff1a; 一、无人机飞行速度概述 无人机的飞行速度通常以其在不同飞行模式下的水平飞行速度来衡量&#xff0c;如平稳挡&#xff08;Cine&#x…

关于武汉高芯coin417G2红外机芯的二次开发

文章目录 前言一、外观和机芯参数二、SDK的使用1、打开相机2、回调函数中获取全局温度和图像3、关闭相机 前言 最近工作中接触了一款基于武汉高芯科技有限公司开发的红外模组,即coin417g2(测温型)9.1mm镜头.使用此模组,开发了一套红外热成像检测桌面应用程序.下面简单记录下该…

踩坑记录(序列化与反序列化)

问题描述 实体类中设定字段名称为 sValue和yValue 返回给前段后,变成了svalue,yvalue 字段设置 测试结果:与字段不符,匹配失败 解决方法 在字段上添加JsonProperty("字段名")注解

乌班图部署若依(nginx)

Nginx 什么是Nginx Nginx&#xff08;发音为"engine x"&#xff09;是由俄罗斯开发者Igor Sysoev创建的一款轻量级、高性能的Web服务器。它首次发布于2004年&#xff0c;如今已成为全球最受欢迎的Web服务器之一。Nginx以其卓越的性能和灵活性而闻名&#xff0c;适用…

[240903] Qwen2-VL: 更清晰地看世界 | Elasticsearch 再次拥抱开源!

目录 Qwen2-VL: 更清晰地看世界Elasticsearch 再次拥抱开源&#xff01; Qwen2-VL: 更清晰地看世界 历经一年研发&#xff0c;阿里云推出新一代视觉语言模型 Qwen2-VL&#xff0c;支持多语言、长视频理解、视觉推理及智能体交互&#xff0c;性能超越 GPT-4o 等模型&#xff0c…

Oracle 常用函数大全

文章目录 一、空校验1. NVL 空校验2. COALESCE 空校验 二、排序1. ORDER BY 排序2. ORDER BY DECODE 指定值排序 三、排名1. RANK 排名2. DENSE RANK 密集排名 四、限制条数1. ROWNUM 限制2. FETCH 限制 五、字符串处理1. TO_CHAR 字符串转换2. || 字符串拼接3. CONCAT 字符串拼…

9.2C++

思维导图

咸鱼代写代码的都是什么人?真的能有收入么?

大家好&#xff0c;我是程序员鱼皮。看到一个帖子&#xff0c;是一位博主分享自己读研期间在咸鱼上靠帮别人代写代码接单的经历。由于内容过于真实&#xff0c;看完之后竟让我有些红温了。 我估计也有很多学编程的同学想自己接单帮别人代写代码&#xff0c;那代写代码真的能有收…

聚水潭ERP集成用友U8(用友U8主供应链)

源系统成集云目标系统 用友U8介绍 用友U8是一套企业级的解决方案&#xff0c;可满足不同的制造、商务模式下&#xff0c;不同运营模式下的企业经营管理。它全面集成了财务、生产制造及供应链的成熟应用&#xff0c;并延伸客户管理至客户关系管理&#xff08;CRM&#xff09;&…

支付宝开放平台-开发者社区——AI 日报「9 月 3 日」

1 逛完世界机器人大会&#xff0c;投资人说再也不想投人形机器人了 腾讯科技丨阅读原文 在2024 年世界机器人大会上&#xff0c;尽管人形机器人成为焦点&#xff0c;但一位长期关注该领域的投资人表示不再考虑投资。原因是目前人形机器人在工业和家用场景中表现不够突出&…