【探花交友DAY 02】项目搭建和用户登录功能的实现

1. 项目介绍

探花交友是一个陌生人的在线交友平台,在该平台中可以搜索附近的人,查看好友动态,平台还会通过大数据计算进行智能推荐,通过智能推荐可以找到更加匹配的好友,这样才能增进用户对产品的喜爱度。探花平台还提供了在线即时通讯功能,可以实时的与好友进行沟通,让沟通随时随地的进行。 项目仓库:https://github.com/liyuxuan7762/tanhuaAPP

1.1 项目功能简介

功能 说明 备注
注册、登录 用户无需单独注册,直接通过手机号登录即可 首次登录成功后需要完善个人信息
交友 主要功能有:测灵魂、桃花传音、搜附近、探花等
圈子 类似微信朋友圈,用户可以发动态、查看好友动态等
消息 通知类消息 + 即时通讯消息
小视频 类似抖音,用户可以发小视频,评论等 显示小视频列表需要进行推荐算法计算后进行展现。
我的 我的动态、关注数、粉丝数、通用设置等

1.2、项目背景

探花交友项目定位于 陌生人交友市场

  • 根据《2018社交领域投融资报告》中指出:虽然相比2017年,投融资事件减少29.5%,但是融资的总额却大幅增长,达到68%。
  • 这些迹象说明:社交领域的发展规模正在扩大,而很多没有特色的产品也会被淘汰。而随着那些尾部产品的倒下,对我们来说就是机会,及时抓住不同社交需求的机会。以社交为核心向不同的细分领域衍生正在逐渐走向成熟化。
  • 而我们按照娱乐形式和内容为主两个维度,将社交行业公司分类为:即时通信、内容社群、陌生人社交、泛娱乐社交以及兴趣社交几个领域。
  • 而在2018年社交的各个细分领域下,均有备受资本所关注的项目,根据烯牛数据2018年的报告中,也同样指出:内容社交及陌生人社交为资本重要关注领域,合计融资占比达73%。

【探花交友DAY 02】项目搭建和用户登录功能的实现_第1张图片
根据市场现状以及融资事件来看:陌生人社交、内容社群、兴趣社交在2019年仍然保持强劲的动力,占到近70%的比例,它们仍然是资本市场主要关注领域。从增长率来看陌生人社交的增长速度远远大于其他几类,因此我们要从这个方向入手。

1.3 技术方案

前端:

  • flutter + android + 环信SDK + redux + shared_preferences + connectivity + iconfont + webview + sqflite

后端:

  • Spring Boot + SpringMVC + Mybatis + MybatisPlus + Dubbo
  • Elasticsearch geo 实现地理位置查询
  • MongoDB 实现海量数据的存储
  • Redis 数据的缓存
  • Spark + MLlib 实现智能推荐
  • 第三方服务 环信即时通讯
  • 第三方服务 阿里云 OSS 、 短信服务
  • 第三方服务 虹软开放平台 / 阿里云

【探花交友DAY 02】项目搭建和用户登录功能的实现_第2张图片

1.5、技术解决方案

  • 使用MongoDB geo实现附近的人的解决方案
  • 使用Spark + Mllib实现智能推荐的解决方案
  • 使用MongoDB进行海量数据的存储的解决方案
  • 使用采用分布式文件系统存储小视频数据的解决方案
  • 使用百度人脸识别的解决方案
  • 使用阿里云进行短信验证码发送的解决方案

2. 前后端分离

项目基于前后端分离的架构进行开发,前后端分离架构总体上包括前端和服务端,通常是多人协作开发

  • 前后端分离开发基于HTTP+JSON交互

  • 通过接口文档(API文档)定义规范

  • 前后端按照文档定义请求及响应数据

【探花交友DAY 02】项目搭建和用户登录功能的实现_第3张图片
在本项目中采用YApi对接口规范进行管理。对于接口的定义我们采用YApi进行管理,YApi是一个开源的接口定义、管理、提供mock数据的管理平台。
【探花交友DAY 02】项目搭建和用户登录功能的实现_第4张图片

3. 开发环境及工具

3.1 开发环境

探花交友项目的开发统一使用提供的Centos7环境,该环境中部署安装了项目所需要的各种服务,如:RabbitMQ,MongoDB、Redis等。我们同意使用Docker对项目所用的到组件进行管理。

3.2 Android模拟器

本项目不同于传统的基于HTML页面的前端项目,采用了安卓APP的方式。因此在本项目中我们使用了网易的MUMU模拟器来运行我们的前端项目。
【探花交友DAY 02】项目搭建和用户登录功能的实现_第5张图片

3.3 POSTMAN

本项目在测试接口的时候使用POSTMAN进行测试。
【探花交友DAY 02】项目搭建和用户登录功能的实现_第6张图片

4. 环境搭建

4.1 MYSQL数据库

一下是本项目所用到的所有的数据表

数据库表 说明
tb_user 用户表
tb_user_info 用户详情表
tb_settings 用户设置表
tb_question 好友问题表
tb_black_list 黑名单
tb_announcement 公告表

4.2 基础服务组件

之前提到本项目中所有的组件都是通过Docker进行管理。为了方便学习与减少基础服务占用的学习时间,全部使用docker-compose的方式集中式部署。这些文件在linux虚拟机中的/root/docker-file文件夹下
/root/docker-file文件夹下包含base, fasdfs, rmq目录,作用如下:

base

  • 其中包含redis,nacos,yapi,mongo

fastdfs

  • 包含fastdfs操作需要的组件

rmq

  • 包含RabbitMQ需要所有组件

每个文件夹中都包含一个docker-compose.yml配置文件,一键启动并部署应用。

#进入组件目录
cd /root/docker-file/base/
#执行docker-compose命令
docker-compose up -d 

4.3 项目工程结构

整体项目使用Maven架构搭建,采用聚合工程形式管理模块,为了便于调用,dubbo需要拆分为接口模块和服务模块整体项目使用Maven架构搭建,采用聚合工程形式管理模块,为了便于调用,dubbo需要拆分为接口模块和服务模块

【探花交友DAY 02】项目搭建和用户登录功能的实现_第7张图片
工程主体结构

父工程 工程名称 说明
tanhua tanhua-autoconfig 自动装配的工具类
tanhua tanhua-domain 实体类模块
tanhua tanhua-dubbo Dubbo子模块(可以理解为文件夹,管理dubbo模块)
tanhua tanhua-app 与手机端交互的入口模块
tanhua-dubbo tanhua-dubbo-interface Dubbo接口模块
tanhua-dubbo tanhua-dubbo-db Dubbo服务模块(数据库部分)
tanhua-dubbo tanhua-dubbo-mongo Dubbo服务模块(MongoDB部分)

模块依赖分析
【探花交友DAY 02】项目搭建和用户登录功能的实现_第8张图片
项目的结构如下如所示:
【探花交友DAY 02】项目搭建和用户登录功能的实现_第9张图片

5. 验证码功能

5.1 需求分析

用户使用手机号进行登录,如果是新用户则需要完善个人信息,并上传头像,在上传头像的时候需要对图像进行校验,判断用户上传的图像是否是人像,防止用户上传非人像图片。流程完成后,则登录成功。

对于已经注册的用户,在验证通过后直接进入到APP主页,对于未注册的用户,在登录成功后需要跳转到完善用户信息界面。

【探花交友DAY 02】项目搭建和用户登录功能的实现_第10张图片

5.2 短信发送代码

由于发送短信需要资质和费用,因此这里使用邮箱发送验证码来替代。之前我们在瑞吉外卖项目中已经实现了通过邮箱发送验证码的代码,这里就不在解释,相关的代码如下:

private boolean sendMailByQQMail(String to, String text, String title) {

    try {
        final Properties props = new Properties();
        props.put("mail.smtp.auth", "true");
//            注意发送邮件的方法中,发送给谁的,发送给对应的app,※
//            要改成对应的app。扣扣的改成qq的,网易的要改成网易的。※
//            props.put("mail.smtp.host", "smtp.qq.com");
        props.put("mail.smtp.host", "smtp.qq.com");

        // 发件人的账号
        props.put("mail.user", emailProperties.getUser());
        //发件人的密码
        props.put("mail.password", emailProperties.getPassword());

        // 构建授权信息,用于进行SMTP进行身份验证
        Authenticator authenticator = new Authenticator() {
            @Override
            protected PasswordAuthentication getPasswordAuthentication() {
                // 用户名、密码
                String userName = props.getProperty("mail.user");
                String password = props.getProperty("mail.password");
                return new PasswordAuthentication(userName, password);
            }
        };
        // 使用环境属性和授权信息,创建邮件会话
        Session mailSession = Session.getInstance(props, authenticator);
        // 创建邮件消息
        MimeMessage message = new MimeMessage(mailSession);
        // 设置发件人
        String username = props.getProperty("mail.user");
        InternetAddress form = new InternetAddress(username);
        message.setFrom(form);

        // 设置收件人
        InternetAddress toAddress = new InternetAddress(to);
        message.setRecipient(Message.RecipientType.TO, toAddress);

        // 设置邮件标题
        message.setSubject(title);

        // 设置邮件的内容体
        message.setContent(text, "text/html;charset=UTF-8");
        // 发送邮件
        Transport.send(message);
        return true;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return false;
}

public void sendCode(String recevier, String code) {
    String text = "欢迎使用探花交友软件,您本次的验证码为" + code + "。(本验证码在1分钟之内有效,请勿将验证码泄露给他人)";
    String title = "探花交友登录验证码";
    sendMailByQQMail(recevier, text, title);
}

相关的依赖如下:

<dependency>
    <groupId>javax.mailgroupId>
    <artifactId>mailartifactId>
    <version>1.4.7version>
dependency>
<dependency>
    <groupId>com.sun.mailgroupId>
    <artifactId>javax.mailartifactId>
    <version>1.5.3version>
dependency>

5.3 实现发送信息组件的自动装配

在实际的企业开发中,对于这种发送短信的通用功能,通常会抽取到一个模块中,其他模块如果想引用的话,可以通过自动装配的方式实现组件的引用。因此接下来我们要改造发送短信的代码,抽取到一个模块中。后续使用的话则通过自动装配的方式。
Springboot自动创配的流程如下:

  1. 扫描依赖模块中的META-INF/spring.factories
  2. 执行装配类中的方法,在IOC容器中创建相关的对象
  3. 在核心工程中通过自动装配的方法使用该对象

在tanhua-autoconfig创建发送短信的工具类

public class EmailTemplate {
//    private static final String USER = "[email protected]"; // 发件人称号,同邮箱地址※
//    private static final String PASSWORD = "ysdimzjzxfqmbdeg"; // 授权码,开启SMTP时显示※

    private EmailProperties emailProperties;

    public EmailTemplate(EmailProperties emailProperties) {
        this.emailProperties = emailProperties;
    }

    private boolean sendMailByQQMail(String to, String text, String title) {

        try {
            final Properties props = new Properties();
            props.put("mail.smtp.auth", "true");
//            注意发送邮件的方法中,发送给谁的,发送给对应的app,※
//            要改成对应的app。扣扣的改成qq的,网易的要改成网易的。※
//            props.put("mail.smtp.host", "smtp.qq.com");
            props.put("mail.smtp.host", "smtp.qq.com");

            // 发件人的账号
            props.put("mail.user", emailProperties.getUser());
            //发件人的密码
            props.put("mail.password", emailProperties.getPassword());

            // 构建授权信息,用于进行SMTP进行身份验证
            Authenticator authenticator = new Authenticator() {
                @Override
                protected PasswordAuthentication getPasswordAuthentication() {
                    // 用户名、密码
                    String userName = props.getProperty("mail.user");
                    String password = props.getProperty("mail.password");
                    return new PasswordAuthentication(userName, password);
                }
            };
            // 使用环境属性和授权信息,创建邮件会话
            Session mailSession = Session.getInstance(props, authenticator);
            // 创建邮件消息
            MimeMessage message = new MimeMessage(mailSession);
            // 设置发件人
            String username = props.getProperty("mail.user");
            InternetAddress form = new InternetAddress(username);
            message.setFrom(form);

            // 设置收件人
            InternetAddress toAddress = new InternetAddress(to);
            message.setRecipient(Message.RecipientType.TO, toAddress);

            // 设置邮件标题
            message.setSubject(title);

            // 设置邮件的内容体
            message.setContent(text, "text/html;charset=UTF-8");
            // 发送邮件
            Transport.send(message);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    public void sendCode(String recevier, String code) {
        String text = "欢迎使用探花交友软件,您本次的验证码为" + code + "。(本验证码在1分钟之内有效,请勿将验证码泄露给他人)";
        String title = "探花交友登录验证码";
        sendMailByQQMail(recevier, text, title);
    }
}

针对发送邮件是需要提供的秘钥等信息,可以写到application.yml中,然后通过@ConfigurationProperties注解读取到一个信息配置类中。然后在EmailTemplate中通过这个类来设置相关的信息。

首先创建一个EmailProperties类来保存秘钥等信息

@Data
@ConfigurationProperties(prefix = "tanhua.email")
public class EmailProperties {
    private String user;
    private String password;
}

@ConfigurationProperties(prefix = "tanhua.email")会从配置文件中读取到相关的属性,然后在IOC容器中创建对象

然后将秘钥等信息写到tanhua-app-server模块下的application.yml

email:
  user: [email protected]
  password: ysdimzjzxfqmbdeg

修改EmailTemplate代码,添加EmailProperties 成员变量和构造方法

public class EmailTemplate {
    private EmailProperties emailProperties;
    public EmailTemplate(EmailProperties emailProperties) {
        this.emailProperties = emailProperties;
    }
    ......
    Authenticator authenticator = new Authenticator() {
                @Override
                protected PasswordAuthentication getPasswordAuthentication() {
                    // 用户名、密码
                    String userName = props.getProperty("mail.user");
                    String password = props.getProperty("mail.password");
                    return new PasswordAuthentication(userName, password);
                }
            };
  

接下来要编写自动装配的配置类,将EmailTemplate对象存入IOC容器

@EnableConfigurationProperties({
        SmsProperties.class,
        EmailProperties.class
})
public class TanhuaAutoConfiguration {
    @Bean
    public EmailTemplate emailTemplate(EmailProperties properties) {
        return new EmailTemplate(properties);
    }
}

最后还要在resource下创建META-INF/spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.tanhua.autoconfig.TanhuaAutoConfiguration

至此,关于发送短信模块的抽取就结束了,下面可以编写测试类进行测试

@RunWith(SpringRunner.class)
@SpringBootTest(classes = AppServerApplication.class)
public class SmsTemplateTest {

    @Autowired
    private EmailTemplate emailTemplate;

    //测试
    @Test
    public void testSendSms() {
        emailTemplate.sendCode("[email protected]", "1234");
    }
}

5.4 登录验证码实现

登录的流程如下:

  • 客户端发送请求
  • 服务端接收大请求,调用短信发送接口
  • 用户端获取相应结果,将结果保存到Redis中
  • 服务端想客户端发送响应信息
    【探花交友DAY 02】项目搭建和用户登录功能的实现_第11张图片
    【探花交友DAY 02】项目搭建和用户登录功能的实现_第12张图片

LoginController.java

@PostMapping("/login")
public ResponseEntity login(@RequestBody Map map) {
    String phone = map.get("phone").toString();
    this.userService.sendMsg(phone);
    return ResponseEntity.ok(null);
}

UseService.java

@Service
public class UserService {

    @Resource
    private EmailTemplate emailTemplate;
    @Resource
    private RedisTemplate<String, String> redisTemplate;
    @DubboReference
    private UserApi userApi;

    public void sendMsg(String phone) {
        // 1. 生成验证码
        String code = RandomStringUtils.randomNumeric(6);
        // 2.调用发送验证码的方法
        emailTemplate.sendCode(phone, code);
        // 3.将验证码存入redis
        redisTemplate.opsForValue().set(VERIFICATION_CODE_PREFIX + phone, code, Duration.ofMinutes(5));
    }
}

这里为了方便开发测试,以后将验证码都写死为123456

6. JWT

6.1 简介

和之前的项目不同,在分布式情况下,我们很难在使用session保存相关的登录信息,转而使用JWT进行登录验证。JSON Web token简称JWT, 是用于对应用程序上的用户进行身份验证的标记。也就是说, 使用 JWTS 的应用程序不再需要保存有关其用户的 cookie 或其他session数据。此特性便于可伸缩性, 同时保证应用程序的安全

6.2 格式

  • JWT就是一个字符串,经过加密处理与校验处理的字符串,形式为:A.B.C

  • A由JWT头部信息header加密得到

  • B由JWT用到的身份验证信息json数据加密得到

  • C由A和B加密得到,是校验部分
    【探花交友DAY 02】项目搭建和用户登录功能的实现_第13张图片

6.3 使用JWT的登录流程

【探花交友DAY 02】项目搭建和用户登录功能的实现_第14张图片

6.4 示例

要使用JWT,我们需要先引用相关的依赖

<dependency>
    <groupId>io.jsonwebtokengroupId>
    <artifactId>jjwtartifactId>
    <version>0.9.1version>
dependency>

编写测试类,实现JWT对数据的封装和根据JWT token解析数据

public class JwtTest {

    @Test
    public void testCreateToken() {
        // 1.准备数据
        Map map = new HashMap();
        map.put("id",1);
        map.put("mobile","13800138000");
        // 2.使用JWT工具生成JWT字符串
        String token = Jwts.builder()
                .signWith(SignatureAlgorithm.HS512, "itcast") // 指定加密算法和秘钥
                .setClaims(map) // 设置数据
                .setExpiration(new Date(System.currentTimeMillis() + 1000000)) // 设置有效期5秒
                .compact();
        System.out.println(token);
    }
    //解析token
    /**
     * SignatureException : token不合法
     * ExpiredJwtException:token已过期
     */
    @Test
    public void testParseToken() {
        try {
            String token = "eyJhbGciOiJIUzUxMiJ9.eyJtb2JpbGUiOiIxMzgwMDEzODAwMCIsImlkIjoxLCJleHAiOjE2NzE1MzE0Njd9.eGljWldVwurcmwkWzc-Jfm6XIsokCVx_TwMazLqiFk0rdabY9ALnbpUEavrqF_maN3FiWl9oOtjZKuJF1rfhUw";
            Claims claims = Jwts.parser()
                    .setSigningKey("itcast") // 设置秘钥
                    .parseClaimsJws(token) // 设置token
                    .getBody();
            Object id = claims.get("id");
            Object code = claims.get("mobile");

            System.out.println(id + "---" + code);
        } catch (SignatureException e){
            System.out.println("token不合法");
        } catch (ExpiredJwtException e) {
            System.out.println("token已过期");
        }
    }
}

这里如果我们设置的token时间已经过了,那么会抛出ExpiredJwtException异常;
如果是token被篡改了,那么会抛出SignatureException异常

为了方便使用,我们可以将JWT的相关操作封装成一个工具类

public class JwtUtils {

    // TOKEN的有效期1小时(S)
    private static final int TOKEN_TIME_OUT = 1 * 3600;

    // 加密KEY
    private static final String TOKEN_SECRET = "itcast";

    // 生成Token
    public static String getToken(Map params){
        long currentTime = System.currentTimeMillis();
        return Jwts.builder()
                .signWith(SignatureAlgorithm.HS512, TOKEN_SECRET) //加密方式
                .setExpiration(new Date(currentTime + TOKEN_TIME_OUT * 1000)) //过期时间戳
                .addClaims(params)
                .compact();
    }
    /**
     * 获取Token中的claims信息
     */
    public static Claims getClaims(String token) {
        return Jwts.parser()
                .setSigningKey(TOKEN_SECRET)
                .parseClaimsJws(token).getBody();
    }
    /**
     * 是否有效 true-有效,false-失效
     */
    public static boolean verifyToken(String token) {
      
        if(StringUtils.isEmpty(token)) {
            return false;
        }
        
        try {
            Claims claims = Jwts.parser()
                    .setSigningKey("itcast")
                    .parseClaimsJws(token)
                    .getBody();
        }catch (Exception e) {
            return false;
        }

      return true;
    }
}

7. 用户登录

在学习完JWT之后,我们来实现用户登录的功能。

7.1 流程分析

【探花交友DAY 02】项目搭建和用户登录功能的实现_第15张图片
【探花交友DAY 02】项目搭建和用户登录功能的实现_第16张图片
首先根据接口编写Controller中的方法

@PostMapping("/loginVerification")
public ResponseEntity loginVerification(@RequestBody Map map) {
    // 1.解析参数
    String phone = map.get("phone").toString();
    String code = map.get("verificationCode").toString();
    // 2.调用Service方法
    Map retMap = this.userService.loginVerification(phone, code);
    // 3.返回结果
    return ResponseEntity.ok(retMap);
}

在UserService中编写登录流程

public Map loginVerification(String phone, String code) {
    // 1.从Redis中获取到验证码
    String redisCode = this.redisTemplate.opsForValue().get(VERIFICATION_CODE_PREFIX + phone);
    // 2.比较验证码
    if (StringUtils.isEmpty(redisCode) || !redisCode.equals(code)) {
        // 验证码无效或者验证码错误
        throw new RuntimeException("验证码失效");
    }
    // 3.判断用户是否已经存在
    User user = userApi.findByMobile(phone);
    // 4.如果不存在则新建用户
    boolean isNew = false;
    if (user == null) {
        // 用户不存在
        user = new User();
        user.setMobile(phone);
        user.setPassword(DigestUtils.md5Hex("123456"));
        Long id = this.userApi.save(user);
        user.setId(id);
        isNew = true;
    }
    // 5.生成Token 保存id和phone
    Map tokenMap = new HashMap();
    tokenMap.put("id", user.getId());
    tokenMap.put("mobile", phone);
    String token = JwtUtils.getToken(tokenMap);
    // 6.封装结果
    Map retMap = new HashMap();
    retMap.put("token", token);
    retMap.put("isNew", isNew);
    return retMap;
}

由于查询和新增用户需要访问数据库,因此需要调用tanhua-dubbo-dbtanhua-dubbo-interface相关代码

首先在tanhua-dubbo-interface中创建API接口

public interface UserApi {
    //根据手机号码查询用户
    User findByMobile(String mobile);
    //保存用户,返回用户id
    Long save(User user);
}

由于我们要访问的是MySQL,所以需要在tanhua-dubbo-db中创建UserApi的实现类

@Override
public User findByMobile(String mobile) {
    QueryWrapper<User> qw = new QueryWrapper<>();
    qw.eq("mobile",mobile);
    return userMapper.selectOne(qw);
}
@Override
public Long save(User user) {
    userMapper.insert(user);
    return user.getId();
}

8. 代码优化

8.1 抽取BasePojo

通过观察数据表发现,很多数据表都有createdupdate 字段,这就意味着对应的实体类中也会有这些字段,每次都需要重复写导致代码冗余。因此我们考虑抽取一个实体类的父类,所有的实体类都继承这个类即可

@Data
public abstract class BasePojo implements Serializable {
    @TableField(fill = FieldFill.INSERT) //自动填充
    private Date created;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updated;
}

8.2 自动填充

对于created和updated字段,每次操作都需要手动设置。为了解决这个问题,mybatis-plus支持自定义处理器的形式实现保存更新的自动填充。

首先需要在实体类的字段上通过@TableField注解指定当前的这个字段在什么情况下自动填充。
然后就需要在tanhua-dubbo-db编写相应的handler实现自动填充

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        Object created = getFieldValByName("created", metaObject);
        if (null == created) {
            //字段为空,可以进行填充
            setFieldValByName("created", new Date(), metaObject);
        }

        Object updated = getFieldValByName("updated", metaObject);
        if (null == updated) {
            //字段为空,可以进行填充
            setFieldValByName("updated", new Date(), metaObject);
        }
    }
    @Override
    public void updateFill(MetaObject metaObject) {
        //更新数据时,直接更新字段
        setFieldValByName("updated", new Date(), metaObject);
    }
}

注意不要忘了加@Component

9. Nacos的一个问题

9.1 问题描述

事情是这样的,为一个项目配置了注册中心nacos,一开始配置的是本机的nacos服务,后面将nacos地址改为虚拟机后,项目虽然启动成功,但是报nacos异常,如下:【探花交友DAY 02】项目搭建和用户登录功能的实现_第17张图片
【探花交友DAY 02】项目搭建和用户登录功能的实现_第18张图片
通过观察错误信息发现一直访问的注册中心地址是localhost,而我们实际上已经在配置文件中配置了nacos的地址。打开nacos可以发现服务实际上已经被注册上去了

9.2 解决方法

这是nacos读取本身自动配置的优先级高于application文件中的配置时引起的,而nacos本身的自动配置是127.0.0.1:8848端口的nacos服务,所以发生了以上异常,故而需要将配置文件的优先级提升

创建一个bootstrap.propertiesbootstrap.yml文件配置nacos地址就可以了。这个配置是系统级的,优先级最高,先从这个文件读取nacos地址就不会报错了

bootstrap.properties

spring.cloud.nacos.server-addr=192.168.136.160:8848

你可能感兴趣的:(Spring,Cloud,Dubbo,探花交友项目,交友,大数据)