头条系统-01-环境搭建、SpringCloud微服务(注册发现、服务调用、网关)

news2024/11/23 10:02:06

文章目录

  • 环境搭建、SpringCloud微服务(注册发现、服务调用、网关)
    • 1)项目介绍
    • 2)项目概述
      • 2.1)学习到的技术内容
      • 2.2)项目课程大纲
      • 2.3)项目概述
      • 2.4)项目术语
      • 2.5)业务说明
    • 3)技术栈
    • 4)nacos环境搭建
      • 4.1)虚拟机镜像准备
        • 注:
      • 4.2)nacos安装
    • 5)初始工程搭建
      • 5.1)环境准备
        • 连接GitHub远程仓库
      • 5.2)主体结构
    • 6)登录
      • 6.1)需求分析
      • 6.2)表结构分析
      • 6.3)思路分析
      • 6.4)**运营端微服务搭建**
        • 6.3.5)看几个预定义类
      • 6.4)登录功能实现
    • 7)接口工具postman、swagger、knife4j
      • 7.1)postman
      • 7.2)swagger
      • 7.3)knife4j (封装了swagger)
    • 8)网关
      • 1.3 全局过滤器实现jwt校验 (校验token)
    • 9)前端集成 (引入前端了)
      • 9.1)前端项目部署思路
      • 9.2)配置nginx

环境搭建、SpringCloud微服务(注册发现、服务调用、网关)

1)项目介绍

类似于今日头条的项目,原型是黑马头条,自己从0开始搭建环境做一遍,然后以自己的方式记录一遍,才能真正转化为自己的东西。主要技术内容如下:
图片描述

2)项目概述

2.1)学习到的技术内容

图片描述

2.2)项目课程大纲

图片描述

2.3)项目概述

随着智能手机的普及,人们更加习惯于通过手机来看新闻。由于生活节奏的加快,很多人只能利用碎片时间来获取信息,因此,对于移动资讯客户端的需求也越来越高。黑马头条项目正是在这样背景下开发出来。黑马头条项目采用当下火热的微服务+大数据技术架构实现。本项目主要着手于获取最新最热新闻资讯,通过大数据分析用户喜好精确推送咨询新闻

2.4)项目术语

图片描述

2.5)业务说明

图片描述

大体分为三个模块:

  • 平台管理

  • 自媒体

  • app端

平台管理与自媒体为PC端,也就电脑浏览器端展示的网页。

其中app端为移动端,也就是手机app, 当然开发时肯定是在浏览器端调成app模式来模拟移动端了。

3)技术栈

图片描述 图片描述
  • Spring-Cloud-Gateway : 微服务之前架设的网关服务,实现服务注册中的API请求路由,以及控制流速控制和熔断处理都是常用的架构手段,而这些功能Gateway天然支持
  • 运用Spring Boot快速开发框架,构建项目工程;并结合Spring Cloud全家桶技术,实现后端个人中心、自媒体、管理中心等微服务。
  • 运用Spring Cloud Alibaba Nacos作为项目中的注册中心和配置中心
  • 运用mybatis-plus作为持久层提升开发效率
  • 运用Kafka完成内部系统消息通知;与客户端系统消息通知;以及实时数据计算
  • 运用Redis缓存技术,实现热数据的计算,提升系统性能指标
  • 使用Mysql存储用户数据,以保证上层数据查询的高性能
  • 使用Mongo存储用户热数据,以保证用户热数据高扩展和高性能指标
  • 使用FastDFS作为静态资源存储器,在其上实现热静态资源缓存、淘汰等功能
  • 运用Hbase技术,存储系统中的冷数据,保证系统数据的可靠性
  • 运用ES搜索技术,对冷数据、文章数据建立索引,以保证冷数据、文章查询性能
  • 运用AI技术,来完成系统自动化功能,以提升效率及节省成本。比如实名认证自动化
  • PMD&P3C : 静态代码扫描工具,在项目中扫描项目代码,检查异常点、优化点、代码规范等,为开发团队提供规范统一,提升项目代码质量

4)nacos环境搭建

如果已经安装过centerOS系统可以略过4.1)
如果已经安装过nacos环境可以略过4.2)

4.1)虚拟机镜像准备

资料网盘:https://www.alipan.com/s/95dVQXeBmyw

1)打开资料文件中的镜像,拷贝到一个地方,然后解压

2)解压后,双击ContOS7-hmtt.vmx文件,前提是电脑上已经安装了VMware
图片描述

已经安装好的虚拟机文件,解压导入即可使用

3)修改虚拟网络地址(NAT)

图片描述

①,选中VMware中的编辑

②,选择虚拟网络编辑器

③,找到NAT网卡,把网段改为200(当前挂载的虚拟机已固定ip地址)

注:

==倘若已经配置过NAT模式了,是无法再配置第二个的,又不想改网段,如下图所示,怎么办? 答:此时我们可以修改虚拟机的静态ip ==

图片描述

我们需要修改虚拟机静态ip在192.168.141.xxx网段里,这里就设置为192.168.141.102吧
如何设置静态ip看这里:linux-02-软件安装-2.2.8 设置静态IP 手动切换到2.2.8小节
资料虚拟机里的root账号密码:itcast
先切换到root账户(需要输入密码),才有权限修改静态ip

以下操作均在虚拟机内进入系统后进行

sudo root
itcast

# 进入网址配置目录
cd /etc/sysconfig/network-scripts/


# 修改内容如下 (千万别多了或少了一个字母,配置错误相当于没有配置静态ip,后果严重)
IPADDR="192.168.141.102"
GATEWAY="192.168.141.2"
NETMASK="255.255.255.0"
DNS1="192.168.141.2"
DNS2="8.8.8.8"
DNS3="8.8.4.4"
ZONE=public

# 修改完成重启网卡
systemctl restart network

# 最后一定要查看一下是否修改成功

在这里插入图片描述
OK成功啦!

下面我们顺便修改root账户密码也为root, 反正是虚拟机没有安全性的必要,简单一点方便记忆。同时可以修改主机名和用户名为自己喜欢的

# 先切换到root账户
su root
itcast

# 再直接用命令修改
passwd
root
root # 输入两次新密码

# 修改主机名称
# hostnamectl set-hostname newName
hostnamectl set-hostname hza

# 修改当前用户名称: 不能用itcast用户登录系统,需要关机后重新输入root/root登录系统
# usermod -l newname oldname
usermod -l hza itcast

修改完成后就可以挂起虚拟机了,下面用finallShell远程连接

4)修改虚拟机的网络模式为NAT
图片描述

5)启动虚拟机,用户名:root 密码:itcast (或者自己修改后的),当前虚拟机的ip已手动固定(静态IP), 地址为:192.168.200.130。 上面我手动修改为192.168.141.102了,所以下面用我修改的

6)使用FinalShell客户端链接

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

4.2)nacos安装

①:docker拉取镜像

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
  • MODE=standalone 单机版

  • –restart=always 开机启动

  • -p 8848:8848 映射端口

  • -d 创建一个守护式容器在后台运行

报错可能是因为已经预先装好了,这里直接启动就行了

③:访问地址:http://192.168.141.102:8848/nacos

图片描述

5)初始工程搭建

5.1)环境准备

①:项目依赖环境(需提前安装好)

  • JDK1.8

  • Intellij Idea

  • maven-3.6.1

  • Git

②:在资料中解压heima-leadnews.zip文件,拷贝到一个没有中文和空格的目录,使用idea打开即可
图片描述

③:IDEA开发工具配置
图片描述

设置本地仓库,建议使用资料中提供好的仓库

④:设置项目编码格式
图片描述

全部改成U8编码

连接GitHub远程仓库

因为gitHub远程仓库默认主分支名称为main而不是master,所以可能得先push一次,这样远程才会有master分支

5.2)主体结构

图片描述

6)登录

6.1)需求分析

图片描述
  • 用户点击开始使用

    登录后的用户权限较大,可以查看,也可以操作(点赞,关注,评论)

  • 用户点击不登录,先看看

​ 游客只有查看的权限

6.2)表结构分析

关于app端用户相关的内容较多,可以单独设置一个库leadnews_user

表名称说明
ap_userAPP用户信息表
ap_user_fanAPP用户粉丝信息表
ap_user_followAPP用户关注信息表
ap_user_realnameAPP实名认证信息表

从当前资料中找到对应数据库并导入到mysql中
参考这里导入数据库:SSM实战-外卖项目-01- 3.1 数据库环境搭建

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

在这里插入图片描述

登录需要用到的是ap_user表,表结构如下:
在这里插入图片描述

在这里插入图片描述

项目中的持久层使用的mybatis-plus,一般都使用mybais-plus逆向生成对应的实体类

app_user表对应的实体类如下:

package cn.whu.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;


@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+随机字符串)

md5是不可逆加密,md5相同的密码每次加密都一样,不太安全。在md5的基础上手动加盐(salt)处理

注册->生成盐
图片描述

登录->使用盐来配合验证
图片描述

6.3)思路分析

图片描述

1,用户输入了用户名和密码进行登录,校验成功后返回jwt(基于当前用户的id生成)

2,用户游客登录,生成jwt返回(基于默认值0生成)‘

6.4)运营端微服务搭建

controller.v1 是为了方便做AB测试,有可能用V1,有可能用V2, 比较二者效果
在heima-leadnews-service下创建工程heima-leadnews-user

图片描述

引导类

package cn.whu.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("cn.whu.user.mapper")
public class UserApplication {

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

bootstrap.yml

server:
  port: 51801
spring:
  application:
    name: leadnews-user # 还有些配置可以放在nacos端,方便热更新  名称就是微服务名(leadnews-user)即可
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.141.102:8848 # 注册发现地址 和配置地址一样 都是nacos
      config:
        server-addr: 192.168.141.102:8848 # 配置地址 和注册发现地址一样 都是nacos
        file-extension: yml # nacos中心有一个 leadnews-user.yml配置文件,也是我这个微服务的配置文件
        # 这也是为啥这个配置文件名称不叫application.yml 而叫 bootstrap.yml的原因
        # 先加载这里的配置,再加载远端配置,远端配置可以覆盖这里的配置

在nacos中创建配置文件

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

注意配置中的密码换成自己数据库的密码,包名改了的也得换

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: 1234 # 注意换成自己的
# 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置
mybatis-plus:
  mapper-locations: classpath*:mapper/*.xml # mybatis的xml方式sql文件
  # 设置别名包扫描路径,通过该属性可以给包中的类注册别名
  type-aliases-package: cn.whu.model.user.pojos

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>
6.3.5)看几个预定义类

枚举和返回Dto的写法,且都没有用lombok,肯定是有道理的

  • 枚举这么写是有道理的
package cn.whu.model.common.enums;

public enum AppHttpCodeEnum {

    // 成功段固定为200
    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;
    }
}
  • 统一的返回结果类
package cn.whu.model.common.dtos;

import com.alibaba.fastjson.JSON;
import cn.whu.model.common.enums.AppHttpCodeEnum;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
 * 通用的结果返回类
 * @param <T>
 */
public class ResponseResult<T> implements Serializable {

    private String host;

    private Integer code;

    private String errorMessage;

    private T data;

    public ResponseResult() {
        this.code = 200;
    }

    public ResponseResult(Integer code, T data) {
        this.code = code;
        this.data = data;
    }

    public ResponseResult(Integer code, String msg, T data) {
        this.code = code;
        this.errorMessage = msg;
        this.data = data;
    }

    public ResponseResult(Integer code, String msg) {
        this.code = code;
        this.errorMessage = msg;
    }

    public static ResponseResult errorResult(int code, String msg) {
        ResponseResult result = new ResponseResult();
        return result.error(code, msg);
    }

    public static ResponseResult okResult(int code, String msg) {
        ResponseResult result = new ResponseResult();
        return result.ok(code, null, msg);
    }

    public static ResponseResult okResult(Object data) {
        ResponseResult result = setAppHttpCodeEnum(AppHttpCodeEnum.SUCCESS, AppHttpCodeEnum.SUCCESS.getErrorMessage());
        if(data!=null) {
            result.setData(data);
        }
        return result;
    }

    public static ResponseResult errorResult(AppHttpCodeEnum enums){
        return setAppHttpCodeEnum(enums,enums.getErrorMessage());
    }

    public static ResponseResult errorResult(AppHttpCodeEnum enums, String errorMessage){
        return setAppHttpCodeEnum(enums,errorMessage);
    }

    public static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums){
        return okResult(enums.getCode(),enums.getErrorMessage());
    }

    private static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums, String errorMessage){
        return okResult(enums.getCode(),errorMessage);
    }

    public ResponseResult<?> error(Integer code, String msg) {
        this.code = code;
        this.errorMessage = msg;
        return this;
    }

    public ResponseResult<?> ok(Integer code, T data) {
        this.code = code;
        this.data = data;
        return this;
    }

    public ResponseResult<?> ok(Integer code, T data, String msg) {
        this.code = code;
        this.data = data;
        this.errorMessage = msg;
        return this;
    }

    public ResponseResult<?> ok(T data) {
        this.data = data;
        return this;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getErrorMessage() {
        return errorMessage;
    }

    public void setErrorMessage(String errorMessage) {
        this.errorMessage = errorMessage;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }


    public static void main(String[] args) {
        //前置
        /*AppHttpCodeEnum success = AppHttpCodeEnum.SUCCESS;
        System.out.println(success.getCode());
        System.out.println(success.getErrorMessage());*/

        //查询一个对象
        /*Map map = new HashMap();
        map.put("name","zhangsan");
        map.put("age",18);
        ResponseResult result = ResponseResult.okResult(map);
        System.out.println(JSON.toJSONString(result));*/


        //新增,修改,删除  在项目中统一返回成功即可
        /*ResponseResult result = ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
        System.out.println(JSON.toJSONString(result));*/


        //根据不用的业务返回不同的提示信息  比如:当前操作需要登录、参数错误
        /*ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN,"自定义提示信息");
        System.out.println(JSON.toJSONString(result));*/

        //查询分页信息
        PageResponseResult responseResult = new PageResponseResult(1,5,50);
        List list = new ArrayList();
        list.add("cn");
        list.add("whu");
        responseResult.setData(list);
        System.out.println(JSON.toJSONString(responseResult));

    }

}

6.4)登录功能实现

〇:Dto创建

其中@ApiModelProperty()只是为了生成swagger文档

package cn.whu.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;
}

①:接口定义

package cn.whu.user.controller.v1;


import cn.whu.model.common.dtos.ResponseResult;
import cn.whu.model.user.dtos.LoginDto;
import cn.whu.user.service.ApUserService;
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;

import javax.annotation.Resource;

@RestController // 包含@Controller和@ResponseBody(将返回对象转为json)两个注解了
@RequestMapping("/api/v1/login")
public class ApUserLoginController {

    @Resource
    private ApUserService apUserService;

    @PostMapping("/login_auth")
    public ResponseResult login(@RequestBody LoginDto dto) {//加上@RequestBody才能接收请求提的json参数,否则只能接受url参数
        return apUserService.login(dto);
    }
}

②:持久层mapper

package cn.whu.user.mapper;

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

@Mapper
public interface ApUserMapper extends BaseMapper<ApUser> {

}

③:业务层service

package cn.whu.user.service;

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

public interface ApUserService extends IService<ApUser> {

    /**
     * app端登陆功能
     * @param dto
     * @return
     */
    public ResponseResult login(LoginDto dto);

}

实现类:

package cn.whu.user.service.impl;

import cn.whu.model.common.dtos.ResponseResult;
import cn.whu.model.common.enums.AppHttpCodeEnum;
import cn.whu.model.user.dtos.LoginDto;
import cn.whu.model.user.pojos.ApUser;
import cn.whu.user.mapper.ApUserMapper;
import cn.whu.user.service.ApUserService;
import cn.whu.utils.common.AppJwtUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;

import java.util.HashMap;
import java.util.Map;

@Service
@Transactional // 事务
@Slf4j
public class ApUserServiceImpl extends ServiceImpl<ApUserMapper, ApUser> implements ApUserService {
    /**
     * app端登陆功能
     *
     * @param dto
     * @return
     */
    @Override
    public ResponseResult login(LoginDto dto) {
        // 1. 正常登录: 用户名 密码
        // 不null 不'' 也不' '
        if (StringUtils.isNotBlank(dto.getPassword()) && StringUtils.isNotBlank(dto.getPhone())) {
            // 1.1 根据手机号查询用户信息
            //ApUser user = getOne(new LambdaQueryWrapper<ApUser>().eq(ApUser::getPhone, dto.getPhone()));
            ApUser dbUser = getOne(Wrappers.<ApUser>lambdaQuery().eq(ApUser::getPhone, dto.getPhone()));
            // Wrappers.<ApUser>lambdaQuery() 就是 new LambdaQueryWrapper<ApUser>()  二者完全一样
            if (dbUser == null) {
                return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST, "该用户不存在");
            }
            // 1.2 比对密码
            String salt = dbUser.getSalt();
            String password = dto.getPassword();
            String pswd = DigestUtils.md5DigestAsHex((password + salt).getBytes());
            if (!pswd.equals(dbUser.getPassword())) {
                return ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_PASSWORD_ERROR);
            }
            // 1.3 返回数据 jwt:就是token    user: 就是user   (二者为data的一个属性)
            String token = AppJwtUtil.getToken(dbUser.getId().longValue());
            Map<String, Object> map = new HashMap<>();
            map.put("token", token);
            dbUser.setSalt("");
            dbUser.setPassword("");//注意有些私密信息不能返回
            map.put("user", dbUser); // 直接返回,会自动序列化

            return ResponseResult.okResult(map); // map作为data返回即可
        }else {
            // 2. 游客登录
            // 没有用户,data中就不需要user,给个token就行了,用id=0L生成
            Map<String, Object> map = new HashMap<>();
            map.put("token",AppJwtUtil.getToken(0L));
            return ResponseResult.okResult(map);
        }
    }
}

④:控制层controller

package cn.whu.user.controller.v1;

import cn.whu.model.common.dtos.ResponseResult;
import cn.whu.model.user.dtos.LoginDto;
import cn.whu.user.service.ApUserService;
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;

import javax.annotation.Resource;

@RestController // 包含@Controller和@ResponseBody(将返回对象转为json)两个注解了
@RequestMapping("/api/v1/login")
public class ApUserLoginController {

    @Resource
    private ApUserService apUserService;

    @PostMapping("/login_auth")
    public ResponseResult login(@RequestBody LoginDto dto) {//加上@RequestBody才能接收请求提的json参数,否则只能接受url参数
        return apUserService.login(dto);
    }
}

7)接口工具postman、swagger、knife4j

7.1)postman

Postman是一款功能强大的网页调试与发送网页HTTP请求的Chrome插件。postman被500万开发者和超100,000家公司用于每月访问1.3亿个API。

官方网址:https://www.postman.com/

解压资料文件夹中的软件,安装即可

通常的接口测试查看请求和响应,下面是登录请求的测试

http://localhost:51801//api/v1/login/login_auth
图片描述

游客登录,没有User

post http://localhost:51801//api/v1/login/login_auth
{
    "host": null,
    "code": 200,
    "errorMessage": "操作成功",
    "data": {
        "token": "eyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAADWLQQrDIBBF7zLrCI5OzJDbmKrEQEAYpS2ld--46ObzPo_3gatX2MEyb_FBzhTCbMgmNEdIVod9KJ6dZ4YFauyw44bkkcLqFpBxaC1v6fmeXkTv8xzKcSTl2JpyfrV_x252VZ39_gDGI42lfgAAAA.4WzHjSPWDMTbyw98JuyfAwP6RikPmuTPU0FaIQtJ3vFu7pw6b65JfXsmomS8rcErwMkQN9ZTjWTE1cRcWk4oeQ"
    }
}
图片描述 用户登录成功,有user信息
请求
post http://localhost:51801//api/v1/login/login_auth
{
    "password":"admin",
    "phone":"13511223456"
}

响应
{
    "host": null,
    "code": 200,
    "errorMessage": "操作成功",
    "data": {
        "user": {
            "id": 4,
            "salt": "",
            "name": "admin",
            "password": "",
            "phone": "13511223456",
            "image": null,
            "sex": true,
            "certification": null,
            "identityAuthentication": null,
            "status": true,
            "flag": 1,
            "createdTime": "2020-03-30T08:36:32.000+00:00"
        },
        "token": "eyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAADXLQQrDIBCF4bvMOkInTozmNmO01EJAGCUtpXfPuMjuezz-H7xbgQ1W5H12LhtPbA2F7Ez0aegZKSX7mJcdJijcYMMVyeJiA04gPWotX2n5GL-IzvPV1dyTmmtV50-9Ox9GV_Sj_wUJzCpJfgAAAA.Rh8l8DeiyY9XCQgSDnQEEtN6v5lbrvvv8rC83i4Skg3z77aZ-TvnWNrH0818dPS4KZ5a-Ylbbs6kD6sAVS49tA"
    }
}

密码错误测试:
在这里插入图片描述
空参游客登录测试
在这里插入图片描述
用户不存在测试:
在这里插入图片描述

7.2)swagger

(1)简介

Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务(https://swagger.io/)。 它的主要作用是:

  1. 使得前后端分离开发更加方便,有利于团队协作

  2. 接口的文档在线自动生成,降低后端开发人员编写接口文档的负担

  3. 功能测试

    Spring已经将Swagger纳入自身的标准,建立了Spring-swagger项目,现在叫Springfox。通过在项目中引入Springfox ,即可非常简单快捷的使用Swagger。

(2)SpringBoot集成Swagger

  • 引入依赖,在heima-leadnews-model和heima-leadnews-common模块中引入该依赖

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

只需要在heima-leadnews-common中进行配置即可,因为其他微服务工程都直接或间接依赖即可。

  • 在heima-leadnews-common工程中添加一个配置类

新增:com.heima.common.swagger.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-leadnews-common模块中的resources目录中新增以下目录和文件

文件:resources/META-INF/Spring.factories
让boot自动配置

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

(3)Swagger常用注解

在Java类中添加Swagger的注解即可生成Swagger接口文档,常用Swagger注解如下:

@Api:修饰整个类,描述Controller的作用

@ApiOperation:描述一个类的一个方法,或者说一个接口

@ApiParam:单个参数的描述信息

@ApiModel:用对象来接收参数

@ApiModelProperty:用对象接收参数时,描述对象的一个字段

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

@ApiResponses:HTTP响应整体描述

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

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

@ApiImplicitParam:一个请求参数

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

@ApiImplicitParam属性:

属性取值作用
paramType查询参数类型
path以地址的形式提交数据
query直接跟参数完成自动映射赋值
body以流的形式提交 仅支持POST
header参数在request headers 里边提交
form以form表单的形式提交 仅支持POST
dataType参数的数据类型 只作为标志说明,并没有实际验证
Long
String
name接收参数名
value接收参数的意义描述
required参数是否必填
true必填
false非必填
defaultValue默认值

我们在ApUserLoginController中添加Swagger注解,代码如下所示:

@RestController // 包含@Controller和@ResponseBody(将返回对象转为json)两个注解了
@RequestMapping("/api/v1/login")
@Api(value = "app端用户登录",tags = "app端用户登录")
public class ApUserLoginController {

    @Resource
    private ApUserService apUserService;

    @PostMapping("/login_auth")
    @ApiOperation("用户登录")
    public ResponseResult login(@RequestBody LoginDto dto) {//加上@RequestBody才能接收请求提的json参数,否则只能接受url参数
        return apUserService.login(dto);
    }
}

LoginDto
(这也是为啥model的pom.xml为何要加入swagger依赖的原因)

@Data
public class LoginDto {

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

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

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

注意切换到default
图片描述

swagger也可以测试:
图片描述
图片描述

7.3)knife4j (封装了swagger)

(1)简介

knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案,前身是swagger-bootstrap-ui,取名kni4j是希望它能像一把匕首一样小巧,轻量,并且功能强悍!

gitee地址:https://gitee.com/xiaoym/knife4j

官方文档:https://doc.xiaominfo.com/

效果演示:http://knife4j.xiaominfo.com/doc.html

(2)核心功能

该UI增强包主要包括两大核心功能:文档说明 和 在线调试

  • 文档说明:根据Swagger的规范说明,详细列出接口文档的说明,包括接口地址、类型、请求示例、请求参数、响应示例、响应参数、响应码等信息,使用swagger-bootstrap-ui能根据该文档说明,对该接口的使用情况一目了然。
  • 在线调试:提供在线接口联调的强大功能,自动解析当前接口参数,同时包含表单验证,调用参数可返回接口响应内容、headers、Curl请求命令实例、响应时间、响应状态码等信息,帮助开发者在线调试,而不必通过其他测试工具测试接口是否正确,简介、强大。
  • 个性化配置:通过个性化ui配置项,可自定义UI的相关显示信息
  • 离线文档:根据标准规范,生成的在线markdown离线文档,开发者可以进行拷贝生成markdown接口文档,通过其他第三方markdown转换工具转换成html或pdf,这样也可以放弃swagger2markdown组件
  • 接口排序:自1.8.5后,ui支持了接口排序功能,例如一个注册功能主要包含了多个步骤,可以根据swagger-bootstrap-ui提供的接口排序规则实现接口的排序,step化接口操作,方便其他开发者进行接口对接

(3)快速集成

  • 在heima-leadnews-common模块中的pom.xml文件中引入knife4j的依赖,如下:
<dependency>
     <groupId>com.github.xiaoymin</groupId>
     <artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
  • 创建Swagger配置文件

在heima-leadnews-common模块中新建配置类

新建Swagger的配置文件Swagger2Configuration.java文件,创建springfox提供的Docket分组对象,代码如下:

package com.heima.common.knife4j;

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();
    }
}

以上有两个注解需要特别说明,如下表:

注解说明
@EnableSwagger2该注解是Springfox-swagger框架提供的使用Swagger注解,该注解必须加
@EnableKnife4j该注解是knife4j提供的增强注解 (封装swagger,提供一些增强功能) ,Ui提供了例如动态参数、参数过滤、接口排序等增强功能,如果你想使用这些增强功能就必须加该注解,否则可以不用加
  • 添加配置

在Spring.factories中新增配置

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.heima.common.swagger.Swagger2Configuration, \
  com.heima.common.swagger.SwaggerConfiguration
  • 访问

在浏览器输入地址:http://host:port/doc.html
http://localhost:51801/doc.html

确实友好多了:
图片描述
图片描述

8)网关

(1)在heima-leadnews-gateway导入以下依赖

pom文件

<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>

(2)在heima-leadnews-gateway下创建heima-leadnews-app-gateway微服务

引导类:

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);
    }
}

bootstrap.yml

server:
  port: 51601
spring:
  application:
    name: leadnews-app-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.200.130:8848
      config:
        server-addr: 192.168.200.130:8848
        file-extension: yml # nacos端存在一个leadnews-app-gateway.yml(yaml)的配置也会加进来

在nacos的配置中心创建dataid为leadnews-app-gateway的yml配置
图片描述

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/** # /user/打头的路由到leadnews-user微服务   访问网关时要加上/user/ 才会匹配到
          filters:
            - StripPrefix= 1 # 转发时去掉1个前缀  真正访问leadnews-user微服务时不会加上/user/前缀,去掉了

环境搭建完成以后,启动项目网关和用户两个服务,使用postman进行测试

请求地址:http://localhost:51601/user/api/v1/login/login_auth
图片描述

1.3 全局过滤器实现jwt校验 (校验token)

图片描述

思路分析:

  1. 用户进入网关开始登陆,网关过滤器进行判断,如果是登录,则路由到后台管理微服务进行登录
  2. 用户登录成功,后台管理微服务签发JWT TOKEN信息返回给用户
  3. 用户再次进入网关开始访问,网关过滤器接收用户携带的TOKEN
  4. 网关过滤器解析TOKEN ,判断是否有权限,如果有,则放行,如果没有则返回未认证错误

具体实现:

第一:

​ 在认证过滤器中需要用到jwt的解析,所以需要把工具类拷贝一份到网关微服务

第二:

在网关微服务中新建全局过滤器:

优先级设置为0最高

package cn.whu.app.gateway.filter;

import cn.whu.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;

@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();

        //2.判断是否是登录
        if(request.getURI().getPath().contains("/login")){
            //放行
            return chain.filter(exchange);
        }


        //3.获取token // 前端传过来的就是"token"
        String token = request.getHeaders().getFirst("token");

        //4.判断token是否存在
        if(StringUtils.isBlank(token)){
            // 返回给前端401
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            // 结束本次请求
            return response.setComplete();
        }

        //5.判断token是否有效
        try {
            Claims claimsBody = AppJwtUtil.getClaimsBody(token);
            //是否是过期
            int result = AppJwtUtil.verifyToken(claimsBody);
            if(result == 1 || result  == 2){ // token 过期 同样返回401
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                return response.setComplete();
            }
        }catch (Exception e){
            e.printStackTrace();
            // token解析失败,也是返回401
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }

        //6.放行 // token解析成功且有效
        return chain.filter(exchange);
    }

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

测试:

启动user服务,继续访问其他微服务,会提示需要认证才能访问,这个时候需要在heads中设置设置token才能正常访问。

9)前端集成 (引入前端了)

9.1)前端项目部署思路

前后端分离,后端只拿动态数据,前端只拿静态页面

图片描述

通过nginx来进行配置,功能如下

  • 通过nginx的反向代理功能访问后台的网关资源
  • 通过nginx的静态服务器功能访问前端静态页面

9.2)配置nginx

①:解压资料文件夹中的压缩包nginx-1.18.0.zip 解压即安装

②:解压资料文件夹中的前端项目/app-web.zip 页面解压到一个单独目录下

③:配置nginx.conf文件

在nginx安装的conf目录下新建一个文件leadnews.conf,在新创建的文件夹中新建heima-leadnews-app.conf文件

heima-leadnews-app.conf配置如下:

注意root后面的静态资源根目录,换成自己app-web.zip解压到的目录

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

server {
	listen 8801;
	location / {
		root C:/software/workspace/app-web/;
		index index.html;
		# 两个配置连在一起,就是首页:C:/software/workspace/app-web/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 把里面注释的内容和静态资源配置相关删除,引入heima-leadnews-app.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,可以直接用上述内容覆盖nginx.conf
否则:就手动在最后加上一个include配置就行了
图片描述

④ :启动nginx

​ 在nginx安装包中使用命令提示符打开,输入命令nginx启动项目

​ 可查看进程,检查nginx是否启动

​ 重新加载配置文件:nginx -s reload

双击无法启动,就在资源管理器上输入cmd,然后执行 nginx命令,啥都不输出就是成功启动
在这里插入图片描述
或者仅仅重新加载一下配置文件即可:
在这里插入图片描述

⑤:打开前端项目进行测试 – > http://localhost:8801

​ 用谷歌浏览器打开,调试移动端模式进行访问
图片描述
点开始使用,能正常跳转,就说明登录成功了:
图片描述
F5刷新后,点不登录,先看看:
图片描述
输入错误的密码或者用户名:
图片描述

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

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

相关文章

LT1964ES5-5 低压差稳压器 200mA 贴片SOT-23-5 使用案例

LT1964ES5-5 微功耗 线性稳压器 LT1964ES5-5 是一款微功耗、低噪声、低dropout的负电压线性稳压器。它的功能是将输入电压转换为稳定的负输出电压&#xff0c;范围在-1.22V到-20V之间&#xff0c;最大输出电流为200mA。该器件特别适用于需要精密调节和低噪声电源的电路中&#…

[详解]Spring AOP

&#x1f3a5; 个人主页&#xff1a;Dikz12&#x1f525;个人专栏&#xff1a;Spring学习之路&#x1f4d5;格言&#xff1a;吾愚多不敏&#xff0c;而愿加学欢迎大家&#x1f44d;点赞✍评论⭐收藏 目录 什么是AOP? Spring AOP 快速入门 Spring AOP核心概念 切点(Point…

普乐蛙元宇宙VR体验馆设备集体亮相VR文旅景区展

普乐蛙全国巡展又双叒叕开始了! 这次来到的是“好客山东”↓↓ 山东2024休闲旅游产业展 4月25日至27日&#xff0c;2024休闲旅游产业展在临沂国际博览中心举办。本次展会以“潮购文旅好品&#xff0c;乐享时尚生活”为主题&#xff0c;汇聚全国文旅产业上下游500多家企业、上万…

SkyWalking 自定义Span并接入告警

图容易被CSDN吞掉&#xff0c;我在掘金也发了&#xff1a;https://juejin.cn/post/7361821913398837248 我就是这么膨胀 最近在做 OpenAI API 套壳&#xff0c;当我使用 okhttp-sse 这个库进行流式内容转发的时候&#xff0c;我发现有些回调方法 SkyWalking 不能抓取到。这就…

Java---数据类型与变量

1.字面常量 字面常量就是我们经常所说的常量&#xff0c;常量即在程序运行期间&#xff0c;固定不变的量。且常量是无法改变的&#xff0c;如果我们的代码有改变常量的操作&#xff0c;程序就会报错。 1.1字面常量的分类 字符串常量&#xff0c;整型常量&#xff0c;浮点数常…

Windows命令行基本命令

目录 什么是相对路径和绝对路径&#xff1f; 一、目录&#xff08;文件夹&#xff09;和文件操作 1.cd命令 用于切换目录 2.dir命令 用于显示目录和文件列表 3.md或mkdir命令 创建文件&#xff0c;也可以创建多级子目录 4.rd命令 用于删除目录 5.move命令 用于移动…

C++|STL-list运用(1)

cplusplus.com/reference/list/list/?kwlist list介绍 list是一个双向循环链表&#xff0c;双向循环链表它的每个节点都有两个链接&#xff0c;一个指向前一个节点&#xff0c;另一个指向下一个节点&#xff0c;且最后一个结点指向头节点。 结点组成 1.数据域 2.指针域 &a…

基于SpringBoot+Vue高校宣讲会管理系统设计与实现

项目介绍&#xff1a; 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装高校宣讲会管理系统软件来发挥其高效地信息…

请编写函数fun,该函数的功能是:将M行N列的二维数组中的数据,按行的顺序依 次放到一维数组中,一维数组中数据的个数存放在形参n所指的存储单元中。

本文收录于专栏:算法之翼 https://blog.csdn.net/weixin_52908342/category_10943144.html 订阅后本专栏全部文章可见。 本文含有题目的题干、解题思路、解题思路、解题代码、代码解析。本文分别包含C语言、C++、Java、Python四种语言的解法完整代码和详细的解析。 题干 请编…

2024年五大企业邮箱最新排名:价格与服务全面对比

在选择企业邮箱时&#xff0c;我们都需要对比企业邮箱价格和邮箱服务。国内五大企业邮箱有Zoho Mail、新浪、网易、腾讯、阿里&#xff0c;这些企业邮箱功能各有偏重点&#xff0c;价格也不一&#xff0c;到底排名如何&#xff1f;我们今天来进行个价格和服务的全面对比。 一、…

家政行业赋能链动:商业模式创新开启全新篇章

大家好&#xff0c;我是微三云周丽&#xff01; 在当今娱乐行业蓬勃发展的背景下&#xff0c;越来越多的年轻人对卫生打理的需求逐渐增加&#xff0c;同时也催生了家政行业的兴起。 然而&#xff0c;如何在激烈的竞争中脱颖而出&#xff0c;成为家政行业面临的重要挑战。本文…

【智能算法】火烈鸟搜索算法(FSA)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献 1.背景 2021年&#xff0c;W Zhiheng等人受到火烈鸟迁徙觅食行为启发&#xff0c;提出了火烈鸟搜索算法&#xff08;Flamingo Search Algorithm, FSA&#xff09;。 2.算法原理 2.1算法思想 FSA受到火烈鸟…

【unity】三维数学应用(计算线和面的交点)

【unity】三维数学应用&#xff08;计算线和面的交点&#xff09; 实现方法有多种&#xff0c;下面介绍一种简单的方法。利用一个点指向面上任意点的向量&#xff0c;到该面法线的投影长度相同的基本原理&#xff0c;结合相似三角形既可以求出交点。 原理 如下图 GD组成的线段…

win11 Windows ADK制作的win pe中没有manage-ade命令或命令无法正常工作解决办法

解决办法 不使用win pe&#xff0c;而是使用Windows安装程序。 将iso镜像烧录到u盘&#xff0c;然后从它引导。 按shift f10弹出cmd&#xff0c;里面存在manage-bde且正常工作。 其他 win pe找不到命令manage-ade 在win pe上使用manage-ade需要一个包 a&#xff0c;adk制作…

计算机网络 备查

OSI 七层模型 七层模型协议各层实现的功能 简要 详细 TCP/IP协议 组成 1.传输层协议 TCP 2.网络层协议 IP 协议数据单元&#xff08;PDU&#xff09;和 封装 数据收发过程 数据发送过程 1. 2.终端用户生成数据 3.数据被分段&#xff0c;并加上TCP头 4.网络层添加IP地址信息…

练习题(2024/4/29)

在深度优先遍历中&#xff1a;有三个顺序&#xff0c;前中后序遍历 这里前中后&#xff0c;其实指的就是中间节点的遍历顺序&#xff0c;只要记住 前中后序指的就是中间节点的位置就可以了。 如图 1二叉树的前序遍历 给你二叉树的根节点 root &#xff0c;返回它节点值的 前…

Vue3+Nuxt3 从0到1搭建官网项目(SEO搜索、中英文切换、图片懒加载)

Vue2Nuxt2 从 0 到1 搭建官网~ 想开发一个官网&#xff0c;并且支持SEO搜索&#xff0c;当然离不开我们的 Nuxt &#xff0c;Nuxt2 我们刚刚可以熟练运用&#xff0c;现在有出现了Nuxt3&#xff0c;那通过本篇文章让我们一起了解一下。 安装 Nuxt3 // npx nuxilatest init &…

乐观锁悲观锁

视频&#xff1a;什么是乐观锁&#xff1f;什么是悲观锁&#xff1f;_哔哩哔哩_bilibili

Leetcode—2739. 总行驶距离【简单】

2024每日刷题&#xff08;121&#xff09; Leetcode—2739. 总行驶距离 实现代码 class Solution { public:int distanceTraveled(int mainTank, int additionalTank) {int consume 0;int ans 0;while(mainTank ! 0) {mainTank--;consume;if(consume 5 && additio…

数据分析案例-全球表面温度数据可视化与统计分析

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…