Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息

想出去旅游,想出去玩,想大吃大喝 0_o



系列文章目录

1. 项目介绍及环境配置
2. 短信验证码登录
3. 用户信息
4. MongoDB
5. 推荐好友列表/MongoDB集群/动态发布与查看
6. 圈子动态/圈子互动
7. 即时通讯(基于第三方API)
8. 附近的人(百度地图APi)
9. 小视频
10.网关配置
11.后台管理


文章目录

  • 系列文章目录
  • 一、业务概述
  • 二、阿里云OSS
    • 1.图片上传服务
    • 2.概述
    • 3.简易案例
    • 4.文件管理
  • 三、封装上传文件组件
    • 1.创建属性配置对象
    • 2.创建模板对象
    • 3.自动装配
    • 4.引入OSS配置
    • 5.测试类
    • 6.下载链接
  • 四、百度人脸识别
    • 1.概述
    • 2.案例
    • 3.测试结果
  • 五.抽取模板工具_人脸识别
    • 1.创建属性配置对象
    • 2.创建模板对象
    • 3.自动装配
    • 4.引入aip配置
    • 5.测试类
    • 6.测试结果
  • 六.保存用户信息
    • 1.接口文档
    • 2.实体类
    • 3.Mapper接口
    • 4.API接口
    • 5. 实现类Impl
    • 6.Controller获取请求参数
    • 7.调用Api保存
  • 七.上传用户头像
    • 1.接口文档
    • 2.控制层接收图片参数
    • 3.逻辑层进行人脸识别
    • 4.Postman测试
  • 八.个人资料管理
    • 1.接口文档
    • 2.获取请求参数
    • 3.调用API查询
    • 4.API接口
    • 5.Impl实现类
    • 6.测试
    • 7.VO
    • 8.VO对象
    • 9.转换查询结果
    • 10.数据相应
    • 11.测试
  • 九.更新个人资料
    • 1.接口文档
    • 2.获取请求参数
    • 3.调用dubboAPI
    • 4.测试
  • 十.拦截器
    • 1.解决方案
    • 2.token拦截器
    • 3.注册TokenInterceptor
    • 4.接口简化(去掉token校验)
    • 5.测试
    • 6.Threadlocal工具类
    • 7.存入ThreadLocal
    • 8.从Threadlocal获取
    • 9.清空ThreadLocal
    • 10.测试
  • 十一.统一异常处理
    • 1.状态码
    • 2.异常对象
    • 3.自定义异常
    • 4.抛出异常
    • 5.测试
    • 6.解决方案
    • 7.自定义统一异常处理
    • 8.简化异常处理(try_catch)
    • 9.测试


一、业务概述

用户在首次登录时需要完善个人信息,包括性别、昵称、生日、城市、头像等。其中,头像数据需要做图片上传,这里采用阿里云的OSS服务作为我们的图片服务器,并且对头像要做人脸识别,非人脸照片不得上传。
Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息_第1张图片

  • 首次登录时(手机号码不存在),需要创建用户存入数据库中
  • 客户端检测首次登录需要完善用户信息
    • 填写用户基本信息
    • 上传用户头像(需要人脸认证)


二、阿里云OSS

1.图片上传服务

实现图片上传服务,需要有存储的支持,有以下几种方案:

  • 直接将图片保存到服务的硬盘(springmvc将的文件上传)
    • 优点:开发便捷,成本低
    • 缺点:扩容困难
  • 使用分布式文件系统进行存储
    • 优点:容易实现扩容
    • 缺点:开发复杂度稍大(有成熟的产品可以使用,比如:FastDFS)
  • 使用第三方的存储服务
    • 优点:开发简单,拥有强大功能,免维护
    • 缺点:付费

Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息_第2张图片


2.概述

对象存储服务(Object Storage Service,OSS)是一种海量、安全、低成本、高可靠的云存储服务,适合存放任意类型的文件。容量和处理能力弹性扩展,多种存储类型供选择,全面优化存储成本。

阿里云官网: https://www.aliyun.com/product/oss

阿里云后台: https://oss.console.aliyun.com/overview

Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息_第3张图片


3.简易案例

官方SDK文档: https://help.aliyun.com/document_detail/84781.html

新建 tanhua-app-server/src/test/java/com/tanhua/test/OssTest.java 测试类:

public class OssTest {

    /**
     * 将资料中的1.jpg上传到阿里云OSS
     * 存放的位置 /yyyy/MM/dd/xxxx.jpg
     */
    @Test
    public void testOss() throws FileNotFoundException {

        // 1. 配置图片路径
        String path = "C:\\Users\\y\\Desktop\\img\\1.jpg";
        // 2. 构造FileInputStream
        FileInputStream inputStream = new FileInputStream(new File(path));
        // 3. 拼接图片路径
        String filename = new SimpleDateFormat("yyyy/MM/dd").format(new Date()) + "/"
                + UUID.randomUUID().toString() + "/" + path.substring(path.lastIndexOf("."));


        // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
        String endpoint = "oss-cn-beijing.aliyuncs.com";
        // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
        String accessKeyId = "LTAI4GKgob9vZ53k2SZdyAC7";
        String accessKeySecret = "LHLBvXmILRoyw0niRSBuXBZewQ30la";
        // 填写Bucket名称,例如examplebucket。
        String bucketName = "examplebucket";
        // 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
        String objectName = "exampledir/exampleobject.txt";
        // 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。
        // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。
        String filePath= "D:\\localpath\\examplefile.txt";

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        try {
            // 创建PutObjectRequest对象。
            // PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, new File(filePath));
            PutObjectRequest putObjectRequest = new PutObjectRequest("tanhua001", filename, inputStream);
            // 如果需要上传时设置存储类型和访问权限,请参考以下示例代码。
            // ObjectMetadata metadata = new ObjectMetadata();
            // metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
            // metadata.setObjectAcl(CannedAccessControlList.Private);
            // putObjectRequest.setMetadata(metadata);

            // 上传文件。
            ossClient.putObject(putObjectRequest);
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }

        String url = "https://tanhua001.oss-cn-beijing.aliyuncs.com/" + filename;
        System.out.println(url);
    }
}

4.文件管理

进入控制台可查看上传的文件:

Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息_第4张图片



三、封装上传文件组件

1.创建属性配置对象

新建 tanhua-autoconfig/src/main/java/com/tanhua/autoconfig/properties/OssProperties.java 文件:

@Data
@ConfigurationProperties(prefix = "tanhua.oss")
public class OssProperties {

    private String accessKey;
    private String secret;
    private String bucketName;
    private String url; //域名
    private String endpoint;
}

2.创建模板对象

新建 tanhua-autoconfig/src/main/java/com/tanhua/autoconfig/template/OssTemplate.java 文件:

public class OssTemplate {

    private OssProperties properties;

    public OssTemplate(OssProperties properties) {
        this.properties = properties;
    }

    /**
     * 文件上传
     * 参数1: 文件名称
     * 参数2: 输入流
     */
    public String upload(String path, InputStream is) {
        // 3. 拼接图片路径
        String filename = new SimpleDateFormat("yyyy/MM/dd").format(new Date()) + "/"
                + UUID.randomUUID().toString() + "/" + path.substring(path.lastIndexOf("."));


        // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
        String endpoint = properties.getEndpoint();
        // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
        String accessKeyId = properties.getAccessKey();
        String accessKeySecret = properties.getSecret();

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        try {
            // 创建PutObjectRequest对象。
            PutObjectRequest putObjectRequest = new PutObjectRequest(properties.getBucketName(), filename, is);
            // 如果需要上传时设置存储类型和访问权限,请参考以下示例代码。
            // ObjectMetadata metadata = new ObjectMetadata();
            // metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
            // metadata.setObjectAcl(CannedAccessControlList.Private);
            // putObjectRequest.setMetadata(metadata);

            // 上传文件。
            ossClient.putObject(putObjectRequest);
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
        String url = properties.getUrl() + "/" + filename;
        return url;
    }
}

3.自动装配

编辑 tanhua-autoconfig/src/main/java/com/tanhua/autoconfig/TanhuaAutoConfiguration.java 文件:

@EnableConfigurationProperties({
        SmsProperties.class,
        OssProperties.class
})
public class TanhuaAutoConfiguration {

    @Bean
    public SmsTemplate smsTemplate(SmsProperties properties) {
        return new SmsTemplate(properties);
    }

    @Bean
    public OssTemplate ossTemplate(OssProperties properties) {
        return new OssTemplate(properties);
    }
}

4.引入OSS配置

编辑 tanhua-app-server/src/main/resources/application.yml 文件:

#服务端口
server:
  port: 18080
spring:
  application:
    name: tanhua-app-server
  redis:  #redis配置
    port: 6379
    host: 192.168.136.160
  cloud:  #nacos配置
    nacos:
      discovery:
        server-addr: 192.168.136.160:8848
dubbo:    #dubbo配置
  registry:
    address: spring-cloud://localhost
  consumer:
    check: false
    retries: 0
tanhua:
  sms:
    signName: 物流云商
    templateCode: SMS_106590012
    accessKey: LTAI5tNBeDJG3SiHQfnMiFuX
    secret: EbuGpw5WKoAR80p7GZj8WelLGtG4Nq
  oss:
    accessKey: LTAI5tNBeDJG3SiHQfnMiFuX
    secret: EbuGpw5WKoAR80p7GZj8WelLGtG4Nq
    endpoint: oss-cn-beijing.aliyuncs.com
    bucketName: tanhua001
    url: https://tanhua001.oss-cn-beijing.aliyuncs.com/
  aip:
    appId: 24021388
    apiKey: ZnMTwoETXnu4OPIGwGAO2H4G
    secretKey: D4jXShyinv5q26bUS78xRKgNLnB9IfZh

5.测试类

新建 tanhua-app-server/src/test/java/com/tanhua/test/OssTest.java 文件:

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

    @Autowired
    private OssTemplate template;

    @Test
    public void testTemplateUpload() throws FileNotFoundException {
        String path = "C:\\Users\\y\\Desktop\\img\\1.jpg";
        FileInputStream inputStream = new FileInputStream(new File(path));
        String imgUrl = template.upload(path, inputStream);
        System.out.println(imgUrl);
    }

    /**
     * 将资料中的1.jpg上传到阿里云OSS
     * 存放的位置 /yyyy/MM/dd/xxxx.jpg
     */
    @Test
    public void testOss() throws FileNotFoundException {

        // 1. 配置图片路径
        String path = "C:\\Users\\y\\Desktop\\img\\1.jpg";
        // 2. 构造FileInputStream
        FileInputStream inputStream = new FileInputStream(new File(path));
        // 3. 拼接图片路径
        String filename = new SimpleDateFormat("yyyy/MM/dd").format(new Date()) + "/"
                + UUID.randomUUID().toString() + "/" + path.substring(path.lastIndexOf("."));


        // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
        String endpoint = "oss-cn-beijing.aliyuncs.com";
        // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
        String accessKeyId = "LTAI4GKgob9vZ53k2SZdyAC7";
        String accessKeySecret = "LHLBvXmILRoyw0niRSBuXBZewQ30la";
        // 填写Bucket名称,例如examplebucket。
        String bucketName = "examplebucket";
        // 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
        String objectName = "exampledir/exampleobject.txt";
        // 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。
        // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。
        String filePath= "D:\\localpath\\examplefile.txt";

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        try {
            // 创建PutObjectRequest对象。
//            PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, new File(filePath));
            PutObjectRequest putObjectRequest = new PutObjectRequest("tanhua001", filename, inputStream);
            // 如果需要上传时设置存储类型和访问权限,请参考以下示例代码。
            // ObjectMetadata metadata = new ObjectMetadata();
            // metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
            // metadata.setObjectAcl(CannedAccessControlList.Private);
            // putObjectRequest.setMetadata(metadata);

            // 上传文件。
            ossClient.putObject(putObjectRequest);
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }

        String url = "https://tanhua001.oss-cn-beijing.aliyuncs.com/" + filename;
        System.out.println(url);
    }
}


6.下载链接

返回的链接可用于图片下载

Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息_第5张图片


四、百度人脸识别

1.概述

人脸识别(Face Recognition)基于图像或视频中的人脸检测、分析和比对技术,提供对您已获授权前提下的私有数据的人脸检测与属性分析、人脸对比、人脸搜索、活体检测等能力。灵活应用于金融、泛安防、零售等行业场景,满足身份核验、人脸考勤、闸机通行等业务需求

官网地址: https://ai.baidu.com/tech/face


2.案例

SDK文档: https://cloud.baidu.com/doc/FACE/s/8k37c1rqz#%E6%96%B0%E5%BB%BAaipface

新建 tanhua-app-server/src/test/java/com/tanhua/test/FaceTest.java 文件:

public class FaceTest {

    //设置APPID/AK/SK
    public static final String APP_ID = "27653276";
    public static final String API_KEY = "zbSWMK1OnrALV6ycE7647Z67";
    public static final String SECRET_KEY = "49SaFjvsS4qajv7CD45e9ExK36L83sts";

    public static void main(String[] args) {
        // 初始化一个AipFace
        AipFace client = new AipFace(APP_ID, API_KEY, SECRET_KEY);

        // 可选:设置网络连接参数
        client.setConnectionTimeoutInMillis(2000);
        client.setSocketTimeoutInMillis(60000);

        // 调用接口
        String image = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fblog%2F202108%2F28%2F20210828122250_3b289.thumb.1000_0.jpg&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002";
        String imageType = "URL";

        // 传入可选参数调用接口
        HashMap<String, String> options = new HashMap<String, String>();
        options.put("face_field", "age");
        options.put("max_face_num", "2");
        options.put("face_type", "LIVE");
        options.put("liveness_control", "LOW");

        // 人脸检测
        JSONObject res = client.detect(image, imageType, options);
        System.out.println(res.toString(2));

        Object error_code = res.get("error_code");
        System.out.println(error_code);
        // => 0 有图片
    }
}

3.测试结果

error_code0 ,表示图片中包含人脸

Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息_第6张图片


五.抽取模板工具_人脸识别

1.创建属性配置对象

新建 tanhua-autoconfig/src/main/java/com/tanhua/autoconfig/properties/AipFaceProperties.java 文件:

@Data
@ConfigurationProperties(prefix = "tanhua.aip")
public class AipFaceProperties {
        private String appId;
        private String apiKey;
        private String secretKey;

    @Bean
    public AipFace aipFace() {
        // 初始化一个AipFace
        AipFace client = new AipFace(appId, apiKey, secretKey);

        // 可选:设置网络连接参数
        client.setConnectionTimeoutInMillis(2000);
        client.setSocketTimeoutInMillis(60000);
        return client;
    }
}

2.创建模板对象

新建 tanhua-autoconfig/src/main/java/com/tanhua/autoconfig/template/AipFaceTemplate.java 文件:

public class AipFaceTemplate {

    @Autowired
    private AipFace client;

    /**
     * 检测图片是否包含人脸
     * true: 包含
     * false: 不包含
     * @return
     */
    public boolean detect(String imageUrl) {
        // 调用接口
        String imageType = "URL";

        // 传入可选参数调用接口
        HashMap<String, String> options = new HashMap<String, String>();
        options.put("face_field", "age");
        options.put("max_face_num", "2");
        options.put("face_type", "LIVE");
        options.put("liveness_control", "LOW");

        // 人脸检测
        JSONObject res = client.detect(imageUrl, imageType, options);
        System.out.println(res.toString(2));

        Integer error_code =(Integer) res.get("error_code");
        return error_code == 0;
    }
}

3.自动装配

编辑 tanhua-autoconfig/src/main/java/com/tanhua/autoconfig/TanhuaAutoConfiguration.java 文件:

@EnableConfigurationProperties({
        SmsProperties.class,
        OssProperties.class,
        AipFaceProperties.class
})
public class TanhuaAutoConfiguration {

    @Bean
    public SmsTemplate smsTemplate(SmsProperties properties) {
        return new SmsTemplate(properties);
    }

    @Bean
    public OssTemplate ossTemplate(OssProperties properties) {return new OssTemplate(properties);}

    @Bean
    public AipFaceTemplate aipFaceTemplate() {return new AipFaceTemplate();}
}

4.引入aip配置

编辑 tanhua-app-server/src/main/resources/application.yml 文件:

#服务端口
server:
  port: 18080
spring:
  application:
    name: tanhua-app-server
  redis:  #redis配置
    port: 6379
    host: 192.168.136.160
  cloud:  #nacos配置
    nacos:
      discovery:
        server-addr: 192.168.136.160:8848
dubbo:    #dubbo配置
  registry:
    address: spring-cloud://localhost
  consumer:
    check: false
    retries: 0
tanhua:
  sms:
    signName: 物流云商
    templateCode: SMS_106590012
    accessKey: LTAI5tNBeDJG3SiHQfnMiFuX
    secret: EbuGpw5WKoAR80p7GZj8WelLGtG4Nq
  oss:
    accessKey: LTAI5tNBeDJG3SiHQfnMiFuX
    secret: EbuGpw5WKoAR80p7GZj8WelLGtG4Nq
    endpoint: oss-cn-beijing.aliyuncs.com
    bucketName: tanhua001
    url: https://tanhua001.oss-cn-beijing.aliyuncs.com/
  aip:
    appId: 24021388
    apiKey: ZnMTwoETXnu4OPIGwGAO2H4G
    secretKey: D4jXShyinv5q26bUS78xRKgNLnB9IfZh

5.测试类

编辑 tanhua-app-server/src/test/java/com/tanhua/test/FaceTest.java 文件:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = AppServerApplication.class)
public class FaceTest {
    @Autowired
    private AipFaceTemplate template;

    @Test
    public void detect() {
        boolean detect = template.detect("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fblog%2F202108%2F28%2F20210828122250_3b289.thumb.1000_0.jpg&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002");
        System.out.println(detect);
    }

    //设置APPID/AK/SK
    public static final String APP_ID = "27653276";
    public static final String API_KEY = "zbSWMK1OnrALV6ycE7647Z67";
    public static final String SECRET_KEY = "49SaFjvsS4qajv7CD45e9ExK36L83sts";

    public static void main(String[] args) {

        // 初始化一个AipFace
        AipFace client = new AipFace(APP_ID, API_KEY, SECRET_KEY);

        // 可选:设置网络连接参数
        client.setConnectionTimeoutInMillis(2000);
        client.setSocketTimeoutInMillis(60000);

        // 调用接口
        String image = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fblog%2F202108%2F28%2F20210828122250_3b289.thumb.1000_0.jpg&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002";
        String imageType = "URL";

        // 传入可选参数调用接口
        HashMap<String, String> options = new HashMap<String, String>();
        options.put("face_field", "age");
        options.put("max_face_num", "2");
        options.put("face_type", "LIVE");
        options.put("liveness_control", "LOW");

        // 人脸检测
        JSONObject res = client.detect(image, imageType, options);
        System.out.println(res.toString(2));

        Object error_code = res.get("error_code");
        System.out.println(error_code);
        // => 0 有图片
    }
}

6.测试结果

返回 trueerror_code == 0 ),则表示图片包含人脸

Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息_第7张图片



六.保存用户信息

1.接口文档

# 连接虚拟机
cd /root/docker-file/base/
# 启动docker镜像
docker-compose up -d

Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息_第8张图片


2.实体类

编辑 tanhua-model/src/main/java/com/tanhua/model/domain/UserInfo.java 文件:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserInfo implements Serializable {

    /**
     * 由于userinfo表和user表之间是一对一关系
     *   userInfo的id来源于user表的id
     */
    @TableId(type= IdType.INPUT)
    private Long id; //用户id
    private String nickname; //昵称
    private String avatar; //用户头像
    private String birthday; //生日
    private String gender; //性别
    private Integer age; //年龄
    private String city; //城市
    private String income; //收入
    private String education; //学历
    private String profession; //行业
    private Integer marriage; //婚姻状态
    private String tags; //用户标签:多个用逗号分隔
    private String coverPic; // 封面图片
    private Date created;
    private Date updated;

    //用户状态,1为正常,2为冻结
    @TableField(exist = false)
    private String userStatus = "1";
}

3.Mapper接口

编辑 tanhua-dubbo/tanhua-dubbo-db/src/main/java/com/tanhua/dubbo/mappers/UserInfoMapper.java 文件:

public interface UserInfoMapper extends BaseMapper<UserInfo> {
}

4.API接口

编辑 tanhua-dubbo/tanhua-dubbo-interface/src/main/java/com/tanhua/dubbo/api/UserInfoApi.java 文件:

public interface UserInfoApi {
    public void save(UserInfo userInfo);

    public void update(UserInfo userInfo);
}

5. 实现类Impl

编辑 tanhua-dubbo/tanhua-dubbo-db/src/main/java/com/tanhua/dubbo/api/UserInfoImpl.java 文件:

@DubboService
public class UserInfoImpl implements UserInfoApi{
    @Autowired
    private UserInfoMapper userInfoMapper;

    @Override
    public void save(UserInfo userInfo) {
        userInfoMapper.insert(userInfo);
    }

    @Override
    public void update(UserInfo userInfo) {
        userInfoMapper.updateById(userInfo);
    }
}

6.Controller获取请求参数

编辑 tanhua-app-server/src/main/java/com/tanhua/server/controller/UserController.java 文件:

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserInfoService userInfoService;

    /**
     * 保存用户信息
     *      UserInfo
     *      请求头中携带的token
     */
    @PostMapping("/loginReginfo")
    public ResponseEntity loginReginfo(@RequestBody UserInfo userInfo,
                                       @RequestHeader("Authorization") String token) {
        // 1. 判断token是否合法
        boolean verifyToken = JwtUtils.verifyToken(token);
        if(!verifyToken) {
            return ResponseEntity.status(401).body(null);
        }

        // 2. 向userInfo中设置用户ID
        Claims claims = JwtUtils.getClaims(token);
        Integer id =(Integer) claims.get("id");
        userInfo.setId(Long.valueOf(id));

        // 3. 调用service
        userInfoService.save(userInfo);
        return ResponseEntity.ok(null);
    }
}

7.调用Api保存

编辑 tanhua-app-server/src/main/java/com/tanhua/server/service/UserInfoService.java 文件:

@Service
public class UserInfoService {

    @DubboReference
    private UserInfoApi userInfoApi;

    public void save(UserInfo userInfo) {
        userInfoApi.save(userInfo);
    }
}


七.上传用户头像

1.接口文档

Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息_第9张图片


2.控制层接收图片参数

编辑 tanhua-app-server/src/main/java/com/tanhua/server/controller/UserController.java 文件:

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserInfoService userInfoService;

    /**
     * 保存用户信息
     *      UserInfo
     *      请求头中携带的token
     */
    @PostMapping("/loginReginfo")
    public ResponseEntity loginReginfo(@RequestBody UserInfo userInfo,
                                       @RequestHeader("Authorization") String token) {
        // 1. 判断token是否合法
        boolean verifyToken = JwtUtils.verifyToken(token);
        if(!verifyToken) {
            return ResponseEntity.status(401).body(null);
        }

        // 2. 向userInfo中设置用户ID
        Claims claims = JwtUtils.getClaims(token);
        Integer id =(Integer) claims.get("id");
        userInfo.setId(Long.valueOf(id));

        // 3. 调用service
        userInfoService.save(userInfo);
        return ResponseEntity.ok(null);
    }

    /**
     * 上传用户头像
     */
    @PostMapping("/loginReginfo/head")
    public ResponseEntity head(MultipartFile headPhoto, @RequestHeader("Authorization") String token) throws IOException {

        // 1. 判断token是否合法
        boolean verifyToken = JwtUtils.verifyToken(token);
        if(!verifyToken) {
            return ResponseEntity.status(401).body(null);
        }

        // 2. 向userInfo中设置用户ID
        Claims claims = JwtUtils.getClaims(token);
        Integer id =(Integer) claims.get("id");

        // 3. 调用service
        userInfoService.updateHead(headPhoto, id);
        return ResponseEntity.ok(null);
    }
}

3.逻辑层进行人脸识别

编辑 tanhua-app-server/src/main/java/com/tanhua/server/service/UserService.java 文件:

@Service
public class UserService {

    @Autowired
    private SmsTemplate template;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @DubboReference
    private UserApi userApi;

    /**
     * 发送短信验证码
     * @param phone
     */

    public void sendMsg(String phone) {
        // 1. 随机生成6位数数字
        // String code = RandomStringUtils.randomNumeric(6);
        // !!! 项目开发不用真正实现短信发送
         String code = "123456";

        // 2. 调用template对象, 发送验证码
        // !!! 项目开发不用真正实现短信发送
        // template.sendSms(phone, code);

        // 3. 将验证码存入redis
        redisTemplate.opsForValue().set("CHECK_CODE_"+phone,code, Duration.ofMinutes(5));
    }

    /**
     * 验证登录
     * @param phone
     * @param code
     */
    public Map loginVerification(String phone, String code) {
        // 1. 从redis中获取验证码
        String redisCode = redisTemplate.opsForValue().get("CHECK_CODE_" + phone);

        // 2. 对验证码进行校验
        if(StringUtils.isEmpty(redisCode) || !redisCode.equals(code)) {
            throw new RuntimeException("验证码错误");
            // throw new BusinessException(ErrorResult.loginError());
        }

        // 3. 删除redis中的验证码
        redisTemplate.delete("CHECK_CODE_" + phone);

        // 4. 通过手机号查询用户
        User user = userApi.findByMobile(phone);
        boolean isNew = false;

        // 5. 如果用户不存在,创建用户保存到数据库
        if(user == null) {
            user = new User();
            user.setMobile(phone);
            // user.setCreated(new Date());
            // user.setUpdated(new Date());
            user.setPassword(DigestUtils.md5Hex("123456"));
            Long userId = userApi.save(user);
            user.setId(userId);
            isNew = true;
        }

        // 6. 通过JWT生成token(存入手机号和用户ID)
        Map tokenMap = new HashMap();
        tokenMap.put("id", user.getId());
        tokenMap.put("mobile", phone);
        String token = JwtUtils.getToken(tokenMap);

        // 7. 构造返回值
        Map retMap = new HashMap();
        retMap.put("token", token);
        retMap.put("isNew", isNew);

        return retMap;
    }
}

4.Postman测试

MUMU模拟器无法实现拍照功能,所以这里使用Postman实现登录、人脸上传功能

  1. 登录
    Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息_第10张图片

  2. 获取token
    Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息_第11张图片

  3. mysql数据库
    Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息_第12张图片

  4. 人脸识别

    将步骤2中获取的 token 填入Value 中:
    Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息_第13张图片
    Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息_第14张图片



八.个人资料管理

1.接口文档

Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息_第15张图片


2.获取请求参数

新建tanhua-app-server/src/main/java/com/tanhua/server/controller/UsersController.java 文件:

@RestController
@RequestMapping("/users")
public class UsersController {

    @Autowired
    private UserInfoService userInfoService;

    /**
     * 查询用户资料
     * 请求头: token
     * 请求参数: userID
     */
    @GetMapping
    public ResponseEntity users(@RequestHeader("Authorization") String token, Long userID) {
        // 1. 判断token是否合法
        boolean verifyToken = JwtUtils.verifyToken(token);
        if(!verifyToken) {
            return ResponseEntity.status(401).body(null);
        }

        // 2. 获取用户ID
        Claims claims = JwtUtils.getClaims(token);
        Integer id =(Integer) claims.get("id");

        // 3. 判断用户ID
        if(userID == null) {
            userID = Long.valueOf(id);
        }

        // 4. 获取用户信息
        UserInfo userInfo = userInfoService.findById(userID);
        return ResponseEntity.ok(userInfo);
    }
}

3.调用API查询

编辑 tanhua-app-server/src/main/java/com/tanhua/server/service/UserInfoService.java 文件:

@Service
public class UserInfoService {

    @DubboReference
    private UserInfoApi userInfoApi;

    @Autowired
    private OssTemplate ossTemplate;

    @Autowired
    private AipFaceTemplate aipFaceTemplate;

    public void save(UserInfo userInfo) {
        userInfoApi.save(userInfo);
    }

    /**
     * 更新用户头像
     * @param headPhoto
     * @param id
     */
    public void updateHead(MultipartFile headPhoto, Integer id) throws IOException {
        // 1. 将图片上传到阿里云OSS
        // String imageUrl = ossTemplate.upload(headPhoto.getOriginalFilename(), headPhoto.getInputStream());
        // !!! 阿里云OSS收费, 这里暂时跳过
        String imageUrl = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fblog%2F202108%2F28%2F20210828122250_3b289.thumb.1000_0.jpg&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002";

        // 2. 调用百度云判断图片是否包含人脸
        boolean detect = aipFaceTemplate.detect(imageUrl);

        // 3. 如果不包含人脸,抛出异常
        if(!detect) {
            throw new RuntimeException("图片不正确");
        // 4. 如果包含人脸,调用aip更新
        } else {
            UserInfo userInfo = new UserInfo();
            userInfo.setId(Long.valueOf(id));
            userInfo.setAvatar(imageUrl);
            userInfoApi.update(userInfo);
        }
    }

    /**
     * 根据id查询
     * @param id
     */
    public UserInfo findById(Long id) {
        return userInfoApi.findById(id);
    }
}

4.API接口

编辑 tanhua-dubbo/tanhua-dubbo-interface/src/main/java/com/tanhua/dubbo/api/UserInfoApi.java 文件:

public interface UserInfoApi {
    public void save(UserInfo userInfo);

    public void update(UserInfo userInfo);

    // 根据id查询
    UserInfo findById(Long id);
}

5.Impl实现类

编辑 tanhua-dubbo/tanhua-dubbo-db/src/main/java/com/tanhua/dubbo/api/UserInfoImpl.java 文件:

@DubboService
public class UserInfoImpl implements UserInfoApi{
    @Autowired
    private UserInfoMapper userInfoMapper;

    @Override
    public void save(UserInfo userInfo) {
        userInfoMapper.insert(userInfo);
    }

    @Override
    public void update(UserInfo userInfo) {
        userInfoMapper.updateById(userInfo);
    }

    @Override
    public UserInfo findById(Long id) {
        return userInfoMapper.selectById(id);
    }
}

6.测试

Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息_第16张图片

Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息_第17张图片


7.VO

根据API文档得知,Java实体和API相应数据 数据类型不统一,导致异常;
例如:实体类中的 ageInteger 类型,而相应数据的 ageString

  • VO(Value Object)值对象,通常用于服务端与界面之间的数据传递。对于一个WEB页面,用一个VO对象对应整个界面的值。
  • DTO(Data Transfer Object)数据传输对象,通常用于界面向服务端的数据传递
  • Entity最常用实体类,基本和数据表一一对应,一个实体一张表(常说的实体类,Domain)

Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息_第18张图片


8.VO对象

新建tanhua-model/src/main/java/com/tanhua/model/vo/UserInfoVo.java 文件:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserInfoVo implements Serializable {

    private Long id; //用户id
    private String nickname; //昵称
    private String avatar; //用户头像
    private String birthday; //生日
    private String gender; //性别
    private String age; //年龄
    private String city; //城市
    private String income; //收入
    private String education; //学历
    private String profession; //行业
    private Integer marriage; //婚姻状态
}

9.转换查询结果

编辑 tanhua-app-server/src/main/java/com/tanhua/server/service/UserInfoService.java 文件:

@Service
public class UserInfoService {

    @DubboReference
    private UserInfoApi userInfoApi;

    @Autowired
    private OssTemplate ossTemplate;

    @Autowired
    private AipFaceTemplate aipFaceTemplate;

    public void save(UserInfo userInfo) {
        userInfoApi.save(userInfo);
    }

    /**
     * 更新用户头像
     * @param headPhoto
     * @param id
     */
    public void updateHead(MultipartFile headPhoto, Integer id) throws IOException {
        // 1. 将图片上传到阿里云OSS
        // String imageUrl = ossTemplate.upload(headPhoto.getOriginalFilename(), headPhoto.getInputStream());
        // !!! 阿里云OSS收费, 这里暂时跳过
        String imageUrl = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fblog%2F202108%2F28%2F20210828122250_3b289.thumb.1000_0.jpg&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002";

        // 2. 调用百度云判断图片是否包含人脸
        boolean detect = aipFaceTemplate.detect(imageUrl);

        // 3. 如果不包含人脸,抛出异常
        if(!detect) {
            throw new RuntimeException("图片不正确");
        // 4. 如果包含人脸,调用aip更新
        } else {
            UserInfo userInfo = new UserInfo();
            userInfo.setId(Long.valueOf(id));
            userInfo.setAvatar(imageUrl);
            userInfoApi.update(userInfo);
        }
    }

    /**
     * 根据id查询
     * @param id
     */
    public UserInfoVo findById(Long id) {
        UserInfo userInfo = userInfoApi.findById(id);
        UserInfoVo vo = new UserInfoVo();

        // vo.setId(userInfo.getId());
        // vo.setNickname(userInfo.getNickname());
        // ...
        BeanUtils.copyProperties(userInfo, vo); // 只会copy同名同类型的属性
        if(userInfo.getAge() != null) {
            vo.setAge(userInfo.getAge().toString());
        }

        return vo;
    }
}

10.数据相应

编辑 tanhua-app-server/src/main/java/com/tanhua/server/controller/UsersController.java 文件:

@RestController
@RequestMapping("/users")
public class UsersController {

    @Autowired
    private UserInfoService userInfoService;

    /**
     * 查询用户资料
     * 请求头: token
     * 请求参数: userID
     */
    @GetMapping
    public ResponseEntity users(@RequestHeader("Authorization") String token, Long userID) {
        // 1. 判断token是否合法
        boolean verifyToken = JwtUtils.verifyToken(token);
        if(!verifyToken) {
            return ResponseEntity.status(401).body(null);
        }

        // 2. 获取用户ID
        Claims claims = JwtUtils.getClaims(token);
        Integer id =(Integer) claims.get("id");

        // 3. 判断用户ID
        if(userID == null) {
            userID = Long.valueOf(id);
        }

        // 4. 获取用户信息
        UserInfoVo userInfo = userInfoService.findById(userID);
        return ResponseEntity.ok(userInfo);
    }
}

11.测试

Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息_第19张图片



九.更新个人资料

1.接口文档

Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息_第20张图片

2.获取请求参数

编辑 tanhua-app-server/src/main/java/com/tanhua/server/controller/UsersController.java 文件:

@RestController
@RequestMapping("/users")
public class UsersController {

    @Autowired
    private UserInfoService userInfoService;

    /**
     * 查询用户资料
     * 请求头: token
     * 请求参数: userID
     */
    @GetMapping
    public ResponseEntity users(@RequestHeader("Authorization") String token, Long userID) {
        // 1. 判断token是否合法
        boolean verifyToken = JwtUtils.verifyToken(token);
        if(!verifyToken) {
            return ResponseEntity.status(401).body(null);
        }

        // 2. 获取用户ID
        Claims claims = JwtUtils.getClaims(token);
        Integer id =(Integer) claims.get("id");

        // 3. 判断用户ID
        if(userID == null) {
            userID = Long.valueOf(id);
        }

        // 4. 获取用户信息
        UserInfoVo userInfo = userInfoService.findById(userID);
        return ResponseEntity.ok(userInfo);
    }

    /**
     * 更新用户资料
     */
    @PutMapping()
    public ResponseEntity updateUserInfo(@RequestBody UserInfo userInfo, @RequestHeader("Authorization") String token) {
        // 1. 判断token是否合法
        boolean verifyToken = JwtUtils.verifyToken(token);
        if(!verifyToken) {
            return ResponseEntity.status(401).body(null);
        }

        // 2. 获取用户ID
        Claims claims = JwtUtils.getClaims(token);
        Integer id =(Integer) claims.get("id");

        // 3. 设置用户ID
        userInfo.setId(Long.valueOf(id));
        userInfoService.update(userInfo);

        return ResponseEntity.ok(null);
    }
}

3.调用dubboAPI

编辑 tanhua-app-server/src/main/java/com/tanhua/server/service/UserInfoService.java 文件:

@Service
public class UserInfoService {

    @DubboReference
    private UserInfoApi userInfoApi;

    @Autowired
    private OssTemplate ossTemplate;

    @Autowired
    private AipFaceTemplate aipFaceTemplate;

    public void save(UserInfo userInfo) {
        userInfoApi.save(userInfo);
    }

    /**
     * 更新用户头像
     * @param headPhoto
     * @param id
     */
    public void updateHead(MultipartFile headPhoto, Integer id) throws IOException {
        // 1. 将图片上传到阿里云OSS
        // String imageUrl = ossTemplate.upload(headPhoto.getOriginalFilename(), headPhoto.getInputStream());
        // !!! 阿里云OSS收费, 这里暂时跳过
        String imageUrl = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fblog%2F202108%2F28%2F20210828122250_3b289.thumb.1000_0.jpg&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002";

        // 2. 调用百度云判断图片是否包含人脸
        boolean detect = aipFaceTemplate.detect(imageUrl);

        // 3. 如果不包含人脸,抛出异常
        if(!detect) {
            throw new RuntimeException("图片不正确");
        // 4. 如果包含人脸,调用aip更新
        } else {
            UserInfo userInfo = new UserInfo();
            userInfo.setId(Long.valueOf(id));
            userInfo.setAvatar(imageUrl);
            userInfoApi.update(userInfo);
        }
    }

    /**
     * 根据id查询
     * @param id
     */
    public UserInfoVo findById(Long id) {
        UserInfo userInfo = userInfoApi.findById(id);
        UserInfoVo vo = new UserInfoVo();

        // vo.setId(userInfo.getId());
        // vo.setNickname(userInfo.getNickname());
        // ...
        BeanUtils.copyProperties(userInfo, vo); // 只会copy同名同类型的属性
        if(userInfo.getAge() != null) {
            vo.setAge(userInfo.getAge().toString());
        }

        return vo;
    }

    /**
     * 更新用户资料
     * @param userInfo
     */
    public void update(UserInfo userInfo) {
        userInfoApi.update(userInfo);
    }
}

4.测试

Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息_第21张图片



十.拦截器

1.解决方案

基于ThreadLocal + 拦截器的形式统一处理
Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息_第22张图片

拦截器(Interceptor)

  • 是一种动态拦截方法调用的机制
  • 类似于Servlet 开发中的过滤器Filter,用于对处理器进行前置处理和后置处理
    Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息_第23张图片

ThreadLocal

  • 线程内部的存储类,赋予了线程存储数据的能力。
  • 线程内调用的方法都可以从ThreadLocal中获取同一个对象。
  • 多个线程中ThreadLocal数据相互隔离

Threadlocal使用方法:

ThreadLocal<T> threadLocal = new ThreadLocal<T>();
threadLocal.set() //将数据绑定到当前线程
threadLocal.get() //从当前线程中获取数据

2.token拦截器

新建 tanhua-app-server/src/main/java/com/tanhua/server/interceptor/TokenInterceptor.java 文件:

public class TokenInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1. 获取请求头
        String token = request.getHeader("Authorization");

        // 2. 使用工具类,判断token是否有效
        boolean verifyToken = JwtUtils.verifyToken(token);

        // 3. 如果token失效,返回状态码401,拦截
        if(!verifyToken) {
            response.setStatus(401);
            return false;
        }

        // 4. 如果token正常可用,放行
        return true;
    }
}

3.注册TokenInterceptor

新建 tanhua-app-server/src/main/java/com/tanhua/server/interceptor/WebConfig.java 文件:

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new TokenInterceptor())
                .addPathPatterns("/**")
                // 排除不需要处理的请求链接
                .excludePathPatterns(new String[]{"/user/login", "/user/loginVerification"});
    }
}

4.接口简化(去掉token校验)

编辑 tanhua-app-server/src/main/java/com/tanhua/server/controller/UsersController.java 文件:

@RestController
@RequestMapping("/users")
public class UsersController {

    @Autowired
    private UserInfoService userInfoService;

    /**
     * 查询用户资料
     * 请求头: token
     * 请求参数: userID
     */
    @GetMapping
    public ResponseEntity users(@RequestHeader("Authorization") String token, Long userID) {
        // 1. 判断token是否合法
        // boolean verifyToken = JwtUtils.verifyToken(token);
        // if(!verifyToken) {
        //     return ResponseEntity.status(401).body(null);
        // }

        // 2. 获取用户ID
        Claims claims = JwtUtils.getClaims(token);
        Integer id =(Integer) claims.get("id");

        // 3. 判断用户ID
        if(userID == null) {
            userID = Long.valueOf(id);
        }

        // 4. 获取用户信息
        UserInfoVo userInfo = userInfoService.findById(userID);
        return ResponseEntity.ok(userInfo);
    }

    /**
     * 更新用户资料
     */
    @PutMapping()
    public ResponseEntity updateUserInfo(@RequestBody UserInfo userInfo, @RequestHeader("Authorization") String token) {
        // 1. 判断token是否合法
        //  boolean verifyToken = JwtUtils.verifyToken(token);
        // if(!verifyToken) {
        //     return ResponseEntity.status(401).body(null);
        // }

        // 2. 获取用户ID
        Claims claims = JwtUtils.getClaims(token);
        Integer id =(Integer) claims.get("id");

        // 3. 设置用户ID
        userInfo.setId(Long.valueOf(id));
        userInfoService.update(userInfo);

        return ResponseEntity.ok(null);
    }
}

编辑 tanhua-app-server/src/main/java/com/tanhua/server/controller/UserController.java 文件:

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserInfoService userInfoService;

    /**
     * 保存用户信息
     *      UserInfo
     *      请求头中携带的token
     */
    @PostMapping("/loginReginfo")
    public ResponseEntity loginReginfo(@RequestBody UserInfo userInfo,
                                       @RequestHeader("Authorization") String token) {
        // 1. 判断token是否合法
        // boolean verifyToken = JwtUtils.verifyToken(token);
        // if(!verifyToken) {
        //     return ResponseEntity.status(401).body(null);
        // }

        // 2. 向userInfo中设置用户ID
        Claims claims = JwtUtils.getClaims(token);
        Integer id =(Integer) claims.get("id");
        userInfo.setId(Long.valueOf(id));

        // 3. 调用service
        userInfoService.save(userInfo);
        return ResponseEntity.ok(null);
    }

    /**
     * 上传用户头像
     */
    @PostMapping("/loginReginfo/head")
    public ResponseEntity head(MultipartFile headPhoto, @RequestHeader("Authorization") String token) throws IOException {

        // 1. 判断token是否合法
        // boolean verifyToken = JwtUtils.verifyToken(token);
        // if(!verifyToken) {
        //     return ResponseEntity.status(401).body(null);
        // }

        // 2. 向userInfo中设置用户ID
        Claims claims = JwtUtils.getClaims(token);
        Integer id =(Integer) claims.get("id");

        // 3. 调用service
        userInfoService.updateHead(headPhoto, id);
        return ResponseEntity.ok(null);
    }
}

5.测试

Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息_第24张图片
Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息_第25张图片

6.Threadlocal工具类

新建 tanhua-app-server/src/main/java/com/tanhua/server/interceptor/UserHolder.java 文件:

/**
 * 工具类: 实现向Threadlocal储存数据的方法
 */
public class UserHolder {

    private static ThreadLocal<User> tl = new ThreadLocal<>();

    // 将用户对象存入Threadlocal
    public static void set(User user) {
        tl.set(user);
    }

    // 从当前线程获取用户对象
    public static User get() {
        return tl.get();
    }

    // 从当前线程获取用户对象的 ID
    public static Long getUserId() {
        return tl.get().getId();
    }

    // 从当前线程获取用户对象的 手机号
    public static String getMobile() {
        return tl.get().getMobile();
    }

    // 清空
    public static void remove() {
        tl.remove();
    }
}

7.存入ThreadLocal

编辑 tanhua-app-server/src/main/java/com/tanhua/server/interceptor/TokenInterceptor.java 文件:

public class TokenInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1. 获取请求头
        String token = request.getHeader("Authorization");

        // 2. 使用工具类,判断token是否有效
        boolean verifyToken = JwtUtils.verifyToken(token);

        // 3. 如果token失效,返回状态码401,拦截
        if(!verifyToken) {
            response.setStatus(401);
            return false;
        }

        // 4. 如果token正常可用,放行

        // 解析token,获取id和手机号,构造User对象,存入ThreadLocal
        Claims claims = JwtUtils.getClaims(token);
        Integer id = (Integer) claims.get("id");
        String mobile = (String) claims.get("mobile");

        User user = new User();
        user.setId(Long.valueOf(id));
        user.setMobile(mobile);

        UserHolder.set(user);

        return true;
    }
}

8.从Threadlocal获取

编辑 tanhua-app-server/src/main/java/com/tanhua/server/controller/UserController.java 文件:

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserInfoService userInfoService;

    /**
     * 保存用户信息
     *      UserInfo
     *      请求头中携带的token
     */
    @PostMapping("/loginReginfo")
    public ResponseEntity loginReginfo(@RequestBody UserInfo userInfo,
                                       @RequestHeader("Authorization") String token) {
        // 1. 判断token是否合法
        // boolean verifyToken = JwtUtils.verifyToken(token);
        // if(!verifyToken) {
        //     return ResponseEntity.status(401).body(null);
        // }

        // 2. 向userInfo中设置用户ID
        // Claims claims = JwtUtils.getClaims(token);
        // Integer id =(Integer) claims.get("id");
        // userInfo.setId(Long.valueOf(id));
         userInfo.setId(UserHolder.getUserId());

        // 3. 调用service
        userInfoService.save(userInfo);
        return ResponseEntity.ok(null);
    }

    /**
     * 上传用户头像
     */
    @PostMapping("/loginReginfo/head")
    public ResponseEntity head(MultipartFile headPhoto, @RequestHeader("Authorization") String token) throws IOException {

        // 1. 判断token是否合法
        // boolean verifyToken = JwtUtils.verifyToken(token);
        // if(!verifyToken) {
        //     return ResponseEntity.status(401).body(null);
        // }

        // 2. 向userInfo中设置用户ID
        // Claims claims = JwtUtils.getClaims(token);
        // Integer id =(Integer) claims.get("id");

        // 3. 调用service
        // userInfoService.updateHead(headPhoto, id);
         userInfoService.updateHead(headPhoto, UserHolder.getUserId());
        return ResponseEntity.ok(null);
    }
}

编辑 tanhua-app-server/src/main/java/com/tanhua/server/controller/UsersController.java 文件:

@RestController
@RequestMapping("/users")
public class UsersController {

    @Autowired
    private UserInfoService userInfoService;

    /**
     * 查询用户资料
     * 请求头: token
     * 请求参数: userID
     */
    @GetMapping
    public ResponseEntity users(@RequestHeader("Authorization") String token, Long userID) {
        // 1. 判断token是否合法
        // boolean verifyToken = JwtUtils.verifyToken(token);
        // if(!verifyToken) {
        //     return ResponseEntity.status(401).body(null);
        // }

        // 2. 获取用户ID
        // Claims claims = JwtUtils.getClaims(token);
        // Integer id =(Integer) claims.get("id");

        // 3. 判断用户ID
        if(userID == null) {
            // userID = Long.valueOf(id);
             userID = UserHolder.getUserId();
        }

        // 4. 获取用户信息
        UserInfoVo userInfo = userInfoService.findById(userID);
        return ResponseEntity.ok(userInfo);
    }

    /**
     * 更新用户资料
     */
    @PutMapping()
    public ResponseEntity updateUserInfo(@RequestBody UserInfo userInfo, @RequestHeader("Authorization") String token) {
        // 1. 判断token是否合法
        //  boolean verifyToken = JwtUtils.verifyToken(token);
        // if(!verifyToken) {
        //     return ResponseEntity.status(401).body(null);
        // }

        // 2. 获取用户ID
        // Claims claims = JwtUtils.getClaims(token);
        // Integer id =(Integer) claims.get("id");

        // 3. 设置用户ID
        // userInfo.setId(Long.valueOf(id));
        userInfo.setId(UserHolder.getUserId());
        userInfoService.update(userInfo);

        return ResponseEntity.ok(null);
    }
}

9.清空ThreadLocal

新建 tanhua-app-server/src/main/java/com/tanhua/server/interceptor/TokenInterceptor.java 文件:

public class TokenInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1. 获取请求头
        String token = request.getHeader("Authorization");

        // 2. 使用工具类,判断token是否有效
        boolean verifyToken = JwtUtils.verifyToken(token);

        // 3. 如果token失效,返回状态码401,拦截
        if(!verifyToken) {
            response.setStatus(401);
            return false;
        }

        // 4. 如果token正常可用,放行

        // 解析token,获取id和手机号,构造User对象,存入ThreadLocal
        Claims claims = JwtUtils.getClaims(token);
        Integer id = (Integer) claims.get("id");
        String mobile = (String) claims.get("mobile");

        User user = new User();
        user.setId(Long.valueOf(id));
        user.setMobile(mobile);

        UserHolder.set(user);

        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserHolder.remove();
    }

}

10.测试

启动项目运行:VMware、FinalShell(启动Doctor)、Navicat(MySQL数据库)、MUMU模拟器、IDEA(Server、Dubbo服务)

Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息_第26张图片


通过MUMU模拟器,启动IDEA断点调试,查看 ThreadLocal 方法是否正常启用

Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息_第27张图片
Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息_第28张图片
Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息_第29张图片



十一.统一异常处理

1.状态码

Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息_第30张图片


2.异常对象

新建 tanhua-model/src/main/java/com/tanhua/model/vo/ErrorResult.java 文件:

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ErrorResult {

    private String errCode = "999999";
    private String errMessage;

    public static ErrorResult error() {
        return ErrorResult.builder().errCode("999999").errMessage("系统异常稍后再试").build();
    }

    public static ErrorResult fail() {
        return ErrorResult.builder().errCode("000001").errMessage("发送验证码失败").build();
    }

    public static ErrorResult loginError() {
        return ErrorResult.builder().errCode("000002").errMessage("验证码失效").build();
    }

    public static ErrorResult faceError() {
        return ErrorResult.builder().errCode("000003").errMessage("图片非人像,请重新上传!").build();
    }

    public static ErrorResult mobileError() {
        return ErrorResult.builder().errCode("000004").errMessage("手机号码已注册").build();
    }

    public static ErrorResult contentError() {
        return ErrorResult.builder().errCode("000005").errMessage("动态内容为空").build();
    }

    public static ErrorResult likeError() {
        return ErrorResult.builder().errCode("000006").errMessage("用户已点赞").build();
    }

    public static ErrorResult disLikeError() {
        return ErrorResult.builder().errCode("000007").errMessage("用户未点赞").build();
    }

    public static ErrorResult loveError() {
        return ErrorResult.builder().errCode("000008").errMessage("用户已喜欢").build();
    }

    public static ErrorResult disloveError() {
        return ErrorResult.builder().errCode("000009").errMessage("用户未喜欢").build();
    }
}

3.自定义异常

新建 tanhua-app-server/src/main/java/com/tanhua/server/exception/BusinessException.java 文件:

/**
 * 自定义异常类
 */
@Data
public class BusinessException extends RuntimeException{

    private ErrorResult errorResult;

    public BusinessException(ErrorResult errorResult) {
        super(errorResult.getErrMessage());
        this.errorResult = errorResult;
    }
}

4.抛出异常

编辑 tanhua-app-server/src/main/java/com/tanhua/server/controller/LoginController.java 文件:

@RestController
@RequestMapping("/user")
public class LoginController {

    @Autowired
    private UserService userService;

    /**
     * 获取登录验证码
     * 请求参数: photo (map)
     * 相应数据: void
     * 返回对象: ResponseEntity
     */
    @PostMapping("/login")
    public ResponseEntity login(@RequestBody Map map) {
        String phone = (String) map.get("phone");
        userService.sendMsg(phone);
        // return ResponseEntity.status(500).body("出错了"); // 指定状态码和返回内容
        return ResponseEntity.ok(null); // 正常返回状态码 200
    }

    /**
     * 校验登录
     * /user/loginVerification
     * phone
     * verificationCode
     */
    @PostMapping("/loginVerification")
    public ResponseEntity loginVerification(@RequestBody Map map) {
        try {
            // 1. 调用map集合获取请求参数
            String phone =(String) map.get("phone");
            String code =(String) map.get("verificationCode");

            // 2. 调用userService完成用户登录
            Map retMap = userService.loginVerification(phone, code);

            // 3. 构造返回
            return ResponseEntity.ok(retMap);

        // 抛出自定义异常
        } catch (BusinessException be) {
            ErrorResult errorResult = be.getErrorResult();
            // return ResponseEntity.status(500).body(errorResult);
             return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResult);

        // 抛出未知异常
        } catch (Exception e) {
           return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ErrorResult.error());
        }
    }
}

编辑 tanhua-app-server/src/main/java/com/tanhua/server/service/UserInfoService.java 文件:

@Service
public class UserInfoService {

    @DubboReference
    private UserInfoApi userInfoApi;

    @Autowired
    private OssTemplate ossTemplate;

    @Autowired
    private AipFaceTemplate aipFaceTemplate;

    public void save(UserInfo userInfo) {
        userInfoApi.save(userInfo);
    }

    /**
     * 更新用户头像
     * @param headPhoto
     * @param id
     */
    // public void updateHead(MultipartFile headPhoto, Integer id) throws IOException {
     public void updateHead(MultipartFile headPhoto, Long id) throws IOException {
        // 1. 将图片上传到阿里云OSS
        // String imageUrl = ossTemplate.upload(headPhoto.getOriginalFilename(), headPhoto.getInputStream());
        // !!! 阿里云OSS收费, 这里暂时跳过
        String imageUrl = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fblog%2F202108%2F28%2F20210828122250_3b289.thumb.1000_0.jpg&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002";

        // 2. 调用百度云判断图片是否包含人脸
        boolean detect = aipFaceTemplate.detect(imageUrl);

        // 3. 如果不包含人脸,抛出异常
        if(!detect) {
            // throw new RuntimeException("图片不正确");
             throw new BusinessException(ErrorResult.faceError());
        // 4. 如果包含人脸,调用aip更新
        } else {
            UserInfo userInfo = new UserInfo();
            userInfo.setId(Long.valueOf(id));
            userInfo.setAvatar(imageUrl);
            userInfoApi.update(userInfo);
        }
    }

    /**
     * 根据id查询
     * @param id
     */
    public UserInfoVo findById(Long id) {
        UserInfo userInfo = userInfoApi.findById(id);
        UserInfoVo vo = new UserInfoVo();

        // vo.setId(userInfo.getId());
        // vo.setNickname(userInfo.getNickname());
        // ...
        BeanUtils.copyProperties(userInfo, vo); // 只会copy同名同类型的属性
        if(userInfo.getAge() != null) {
            vo.setAge(userInfo.getAge().toString());
        }

        return vo;
    }

    /**
     * 更新用户资料
     * @param userInfo
     */
    public void update(UserInfo userInfo) {
        userInfoApi.update(userInfo);
    }
}

编辑 tanhua-app-server/src/main/java/com/tanhua/server/service/UserService.java 文件:

@Service
public class UserService {

    @Autowired
    private SmsTemplate template;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @DubboReference
    private UserApi userApi;

    /**
     * 发送短信验证码
     * @param phone
     */

    public void sendMsg(String phone) {
        // 1. 随机生成6位数数字
        // String code = RandomStringUtils.randomNumeric(6);
        // !!! 项目开发不用真正实现短信发送
         String code = "123456";

        // 2. 调用template对象, 发送验证码
        // !!! 项目开发不用真正实现短信发送
        // template.sendSms(phone, code);

        // 3. 将验证码存入redis
        redisTemplate.opsForValue().set("CHECK_CODE_"+phone,code, Duration.ofMinutes(5));
    }

    /**
     * 验证登录
     * @param phone
     * @param code
     */
    public Map loginVerification(String phone, String code) {
        // 1. 从redis中获取验证码
        String redisCode = redisTemplate.opsForValue().get("CHECK_CODE_" + phone);

        // 2. 对验证码进行校验
        if(StringUtils.isEmpty(redisCode) || !redisCode.equals(code)) {
            // throw new RuntimeException("验证码错误");
            throw new BusinessException(ErrorResult.loginError());
        }

        // 3. 删除redis中的验证码
        redisTemplate.delete("CHECK_CODE_" + phone);

        // 4. 通过手机号查询用户
        User user = userApi.findByMobile(phone);
        boolean isNew = false;

        // 5. 如果用户不存在,创建用户保存到数据库
        if(user == null) {
            user = new User();
            user.setMobile(phone);
            // user.setCreated(new Date());
            // user.setUpdated(new Date());
            user.setPassword(DigestUtils.md5Hex("123456"));
            Long userId = userApi.save(user);
            user.setId(userId);
            isNew = true;
        }

        // 6. 通过JWT生成token(存入手机号和用户ID)
        Map tokenMap = new HashMap();
        tokenMap.put("id", user.getId());
        tokenMap.put("mobile", phone);
        String token = JwtUtils.getToken(tokenMap);

        // 7. 构造返回值
        Map retMap = new HashMap();
        retMap.put("token", token);
        retMap.put("isNew", isNew);

        return retMap;
    }
}

5.测试

Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息_第31张图片


6.解决方案

SpringMVC提供了一套解决全局异常的处理方案,可以在代码无侵入的前提下完成异常处理。遵循逐层抛出,异常处理器统一处理的思路

Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息_第32张图片

  • 项目中可能存在不可预知的各种异常,如:空指针,数组越界等。针对这类异常,可以直接在异常处理器中统一处理
  • 还有一类是可预知的错误,如图片不合法,验证码错误等等。这类错误也可以理解为业务异常,可以通过自定义异常类来处理;

7.自定义统一异常处理

编辑 tanhua-app-server/src/main/java/com/tanhua/server/exception/ExceptionAdvice.java 文件:

/**
 * 自定义统一异常处理
 */
// 1. 通过注解,声明异常处理类
@ControllerAdvice
public class ExceptionAdvice {

    // 2. 方法上编写注解,指定此方法可以处理的异常类型
    @ExceptionHandler(BusinessException.class)

    // 3. 编写方法,在方法内部处理异常,构造响应数据
    public ResponseEntity handlerException(BusinessException be) {
        // 打印报错信息
        be.printStackTrace();
        ErrorResult errorResult = be.getErrorResult();
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResult);
    }

    // 4. 处理未知错误
    @ExceptionHandler(Exception.class)
    public ResponseEntity handlerException1(Exception be) {
        be.printStackTrace();
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ErrorResult.error());
    }
}

8.简化异常处理(try_catch)

编辑 tanhua-app-server/src/main/java/com/tanhua/server/controller/LoginController.java 文件:

@RestController
@RequestMapping("/user")
public class LoginController {

    @Autowired
    private UserService userService;

    /**
     * 获取登录验证码
     * 请求参数: photo (map)
     * 相应数据: void
     * 返回对象: ResponseEntity
     */
    @PostMapping("/login")
    public ResponseEntity login(@RequestBody Map map) {
        String phone = (String) map.get("phone");
        userService.sendMsg(phone);
        // return ResponseEntity.status(500).body("出错了"); // 指定状态码和返回内容
        return ResponseEntity.ok(null); // 正常返回状态码 200
    }

    /**
     * 校验登录
     * /user/loginVerification
     * phone
     * verificationCode
     */
    @PostMapping("/loginVerification")
    public ResponseEntity loginVerification(@RequestBody Map map) {
        int i = 1/0; // 测试抛出异常(记得注释掉)

        // 1. 调用map集合获取请求参数
        String phone = (String) map.get("phone");
        String code = (String) map.get("verificationCode");

        // 2. 调用userService完成用户登录
        Map retMap = userService.loginVerification(phone, code);

        // 3. 构造返回
        return ResponseEntity.ok(retMap);
    }
}

9.测试

Dobbo微服务项目实战(详细介绍+案例源码) - 3.用户信息_第33张图片

你可能感兴趣的:(Java,微服务,java,dubbo,分布式,云原生,1024程序员节)