探花交友

探花交友

**技术架构:**Spring Boot + SpringMVC + Mybatis + MybatisPlus + Dubbo

项目负责:

  1. 用户注册登录认证,jwt实现单点登录功能;

  2. 用户圈子模块,实现用户的发布动态、查询动态、点赞、喜欢和评论模块的编写;

  3. 用户今日最佳好友模块查询;

  4. 图片上传功能,存储用户的头像。

技术亮点:

​ 1. 圈子模块使用mongodb存储海量数据;

​ 2. 使用阿里云短信平台发送验证码,Redis存储验证码;

​ 3. 使用rocketmq消息中间件发送用户操作;

​ 4. 使用阿里云oss存储照片;

  1. 实现统一增加缓存逻辑的实现,减少数据库的压力;

  2. 通过拦截器+ThreadLocal的方式统一解决token。

注册登录

业务说明:

用户通过手机验证码进行登录,如果是第一次登录则需要完善个人信息,在上传图片时,需要对上传的图片做人像的校验,防止用户上传非人像的图片作为头像。流程完成后,则登录成功。

发送手机验证码:使用阿里云平台短信服务发送

验证用户登录

后台需要验证手机号与验证码是否正确

首次登录需要完善个人信息

校验token是否有效

校验存储到redis的token是否有效

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vROqlI4X-1619110228803)(探花交友.assets/image-20210406202507769.png)]

校验用户登录流程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ScqmXKuf-1619110228805)(探花交友.assets/image-20210406202649388.png)]

CREATE TABLE `tb_user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `mobile` varchar(11) DEFAULT NULL COMMENT '手机号',
  `password` varchar(32) DEFAULT NULL COMMENT '密码,需要加密',
  `created` datetime DEFAULT NULL,
  `updated` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `mobile` (`mobile`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用户表';

CREATE TABLE `tb_user_info` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) NOT NULL COMMENT '用户id',
  `nick_name` varchar(50) DEFAULT NULL COMMENT '昵称',
  `logo` varchar(100) DEFAULT NULL COMMENT '用户头像',
  `tags` varchar(50) DEFAULT NULL COMMENT '用户标签:多个用逗号分隔',
  `sex` int(1) DEFAULT '3' COMMENT '性别,1-男,2-女,3-未知',
  `age` int(11) DEFAULT NULL COMMENT '用户年龄',
  `edu` varchar(20) DEFAULT NULL COMMENT '学历',
  `city` varchar(20) DEFAULT NULL COMMENT '居住城市',
  `birthday` varchar(20) DEFAULT NULL COMMENT '生日',
  `cover_pic` varchar(50) DEFAULT NULL COMMENT '封面图片',
  `industry` varchar(20) DEFAULT NULL COMMENT '行业',
  `income` varchar(20) DEFAULT NULL COMMENT '收入',
  `marriage` varchar(20) DEFAULT NULL COMMENT '婚姻状态',
  `created` datetime DEFAULT NULL,
  `updated` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户信息表';
# Redis相关配置
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait = 5000ms
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-Idle = 100
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-Idle = 10
# 连接超时时间(毫秒)
spring.redis.timeout = 10s
spring.redis.cluster.nodes = 192.168.31.81:6379,192.168.31.81:6380,192.168.31.81:6381
spring.redis.cluster.max-redirects=5

# RocketMQ相关配置
rocketmq.name-server=192.168.31.81:9876
rocketmq.producer.group=tanhua

#itcast_tanhua
#盐 值
jwt.secret=76bd425b6f29f7fcc2e0bfc286043df1

#虹软相关配置
arcsoft.appid=*****
arcsoft.sdkKey=****
arcsoft.libPath=F:\\code\\WIN64
 /**

- 发送验证码
- @param mobile
- @return
/
    public Map<String, Object> sendCheckCode(String mobile) {
Map<String, Object> result = new HashMap<>(2);
try {
    String redisKey = "CHECK_CODE_" + mobile;
    String value = this.redisTemplate.opsForValue().get(redisKey);
    if (StringUtils.isNotEmpty(value)) {
        result.put("code", 1);
        result.put("msg", "上一次发送的验证码还未失效");
        return result;
    }
    String code = this.sendSms(mobile);
    if (null == code) {
        result.put("code", 2);
        result.put("msg", "发送短信验证码失败");
        return result;
    }

//发送验证码成功
        result.put("code", 3);
        result.put("msg", "ok");

        //将验证码存储到Redis,2分钟后失效
        this.redisTemplate.opsForValue().set(redisKey, code, Duration.ofMinutes(2));

        return result;
    } catch (Exception e) {

        LOGGER.error("发送验证码出错!" + mobile, e);

        result.put("code", 4);
        result.put("msg", "发送验证码出现异常");
        return result;
    }

}

用户登录

用户接收到验证码后,进行输入验证码,点击登录,前端系统将手机号以及验证码提交到SSO进行校验。

@Service
public class UserService {

private static final Logger LOGGER = LoggerFactory.getLogger(UserService.class);

@Autowired
private UserMapper userMapper;

@Autowired
private RedisTemplate<String, String> redisTemplate;

private static final ObjectMapper MAPPER = new ObjectMapper();

@Autowired
private RocketMQTemplate rocketMQTemplate;

@Value("${jwt.secret}")
private String secret;

public String login(String mobile, String code) {

    Boolean isNew = false; //是否为新注册

    //校验验证码
    String redisKey = "CHECK_CODE_" + mobile;
    String value = this.redisTemplate.opsForValue().get(redisKey);
    //判断是否为空
    if (!StringUtils.equals(value, code)) {
        return null; //验证码错误
    }

    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("mobile", mobile);
    User selectUser = this.userMapper.selectOne(queryWrapper);
    if (selectUser == null) {
        // 该手机号未注册,进行注册操作
        User user = new User();
        user.setMobile(mobile);
        user.setPassword(DigestUtils.md5Hex(secret + "_123456"));// 默认密码
        this.userMapper.insert(user);
        selectUser = user;
        isNew = true;
    }

    Map<String, Object> claims = new HashMap<String, Object>();
    claims.put("mobile", mobile);
    claims.put("id", selectUser.getId());

    // 生成token
    String token = Jwts.builder()
            .setClaims(claims) //设置响应数据体
            .signWith(SignatureAlgorithm.HS256, secret) //设置加密方法和加密盐
            .compact();

    //将用户数据写入到redis中
    String redisTokenKey = "TOKEN_" + token;
    try {
        this.redisTemplate.opsForValue().set(redisTokenKey, MAPPER.writeValueAsString(selectUser), Duration.ofHours(1));
    } catch (JsonProcessingException e) {
        e.printStackTrace();
    }

    try {
        // 发送登录成功的消息
        Map<String, Object> msg = new HashMap<>();
        msg.put("userId", selectUser.getId());
        msg.put("date", new Date());
        this.rocketMQTemplate.convertAndSend("tanhua-sso-login", msg);
        //topic为tanhua-sso-login
    } catch (Exception e) {
        e.printStackTrace();
    }
    return isNew + "|" + token;
}
}

图片上传

阿里云OSS存储

导入依赖

com.aliyun.oss aliyun-sdk-oss 2.8.3
OSS配置
aliyun.properties:

aliyun.endpoint = http://oss-cn-zhangjiakou.aliyuncs.com
aliyun.accessKeyId = LTAI4FuH6QpFxcsEb6boSRn2
aliyun.accessKeySecret = 5fmPjtxxCPfIBznMzN5KE0wz9p0t1B
aliyun.bucketName= tanhua-dev
aliyun.urlPrefix=http://tanhua-dev.oss-cn-zhangjiakou.aliyuncs.com/

AliyunConfig:

package com.tanhua.sso.config;

import com.aliyun.oss.OSSClient;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Configuration
@PropertySource("classpath:aliyun.properties")
@ConfigurationProperties(prefix = "aliyun")
@Data
public class AliyunConfig {

private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
private String urlPrefix;

@Bean
public OSSClient oSSClient() {
    return new OSSClient(endpoint, accessKeyId, accessKeySecret);
}
}

PicUploadService

package com.tanhua.sso.service;

import com.aliyun.oss.OSSClient;
import com.tanhua.sso.config.AliyunConfig;
import com.tanhua.sso.vo.PicUploadResult;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;

@Service
public class PicUploadService {

// 允许上传的格式
private static final String[] IMAGE_TYPE = new String[]{".bmp", ".jpg",
        ".jpeg", ".gif", ".png"};

@Autowired
private OSSClient ossClient;

@Autowired
private AliyunConfig aliyunConfig;

public PicUploadResult upload(MultipartFile uploadFile) {

    PicUploadResult fileUploadResult = new PicUploadResult();

    //图片做校验,对后缀名
    boolean isLegal = false;

    for (String type : IMAGE_TYPE) {
        if (StringUtils.endsWithIgnoreCase(uploadFile.getOriginalFilename(),
                type)) {
            isLegal = true;
            break;
        }
    }

    if (!isLegal) {
        fileUploadResult.setStatus("error");
        return fileUploadResult;
    }

    // 文件新路径
    String fileName = uploadFile.getOriginalFilename();
    String filePath = getFilePath(fileName);

    // 上传到阿里云
    try {
        // 目录结构:images/2018/12/29/xxxx.jpg
        ossClient.putObject(aliyunConfig.getBucketName(), filePath, new
                ByteArrayInputStream(uploadFile.getBytes()));
    } catch (Exception e) {
        e.printStackTrace();
        //上传失败
        fileUploadResult.setStatus("error");
        return fileUploadResult;
    }

    // 上传成功
    fileUploadResult.setStatus("done");
    fileUploadResult.setName(this.aliyunConfig.getUrlPrefix() + filePath);
    fileUploadResult.setUid(String.valueOf(System.currentTimeMillis()));

    return fileUploadResult;
}

private String getFilePath(String sourceFileName) {
    DateTime dateTime = new DateTime();
    return "images/" + dateTime.toString("yyyy")
            + "/" + dateTime.toString("MM") + "/"
            + dateTime.toString("dd") + "/" + System.currentTimeMillis() +
            RandomUtils.nextInt(100, 9999) + "." +
            StringUtils.substringAfterLast(sourceFileName, ".");
}

}

检查登录状态

为其他系统提供根据token来查询用户信息的接口。

 public User queryUserByToken(String token) {
        try {
            String redisTokenKey = "TOKEN_" + token;
            String cacheData = this.redisTemplate.opsForValue().get(redisTokenKey);
            if (StringUtils.isEmpty(cacheData)) {
                return null;
            }
            // 刷新时间
            this.redisTemplate.expire(redisTokenKey, 1, TimeUnit.HOURS);
            return MAPPER.readValue(cacheData, User.class);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

首页

在用户登录成功后,就会进入首页,首页中有今日佳人、推荐好友、探花、搜附近等功能。

今日佳人

今日佳人,会推荐缘分值最大的用户,进行展现出来。缘分值的计算是由用户的行为进行打分,如:点击、点赞、评论、学历、婚姻状态等信息组合而成的。

实现:我们先不考虑推荐的逻辑,假设现在已经有推荐的结果,我们只需要从结果中查询到缘分值最高的用户就可以了。至于推荐的逻辑以及实现,我们将后面的课程中讲解。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VOPr94tM-1619110228807)(探花交友.assets/image-20210406214654253.png)]

表结构设计

#表结构
{
  "userId":1001,  #推荐的用户id
  "toUserId":1002, #用户id
  "score":90,  #推荐得分
  "date":"2019/1/1" #日期
}


@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(collection = "recommend_user")
public class RecommendUser implements java.io.Serializable{

    private static final long serialVersionUID = -4296017160071130962L;

    @Id
    private ObjectId id; //主键id
    @Indexed
    private Long userId; //推荐的用户id
    private Long toUserId; //用户id
    @Indexed
    private Double score; //推荐得分
    private String date; //日期
}

搭建工程 dubbo服务

系统采用Dubbo构建,首先开发的是dubbo服务工程。

 <!--dubbo的springboot支持-->
        <dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>0.2.0</version>
        </dependency>
        <!--dubbo框架-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.6.4</version>
        </dependency>
        <!--zk依赖-->
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.13</version>
        </dependency>
        <dependency>
            <groupId>com.github.sgroschupf</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.1</version>
        </dependency>

        <!--MongoDB相关依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mongodb</groupId>
            <artifactId>mongodb-driver-sync</artifactId>
            <version>3.9.1</version>
        </dependency>
        
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.32.Final</version>
        </dependency>
########### ZK的部署安装 #################

#拉取zk镜像
docker pull zookeeper:3.5
#创建容器
docker create --name zk -p 2181:2181 zookeeper:3.5
#启动容器
docker start zk
@Service(version = "1.0.0")
public class RecommendUserApiImpl implements RecommendUserApi {
@Autowired
private MongoTemplate mongoTemplate;

@Override
public RecommendUser queryWithMaxScore(Long userId) {
    Query query = Query.query(Criteria.where("toUserId").is(userId))
            .with(Sort.by(Sort.Order.desc("score"))).limit(1);
    return this.mongoTemplate.findOne(query, RecommendUser.class);
}

@Override
public PageInfo<RecommendUser> queryPageInfo(Long userId, Integer pageNum, Integer pageSize) {
    PageRequest pageRequest = PageRequest.of(pageNum - 1, pageSize, Sort.by(Sort.Order.desc("score")));
    Query query = Query.query(Criteria.where("toUserId").is(userId)).with(pageRequest);
    List<RecommendUser> recommendUserList = this.mongoTemplate.find(query, RecommendUser.class);
    // 数据总数暂不提供,如前端需要再实现
    return new PageInfo<>(0, pageNum, pageSize, recommendUserList);
}

统一接口服务入口

现在我们有sso和server需要对外提供接口服务,而在前端只能设置一个请求地址,所以我们需要将服务接口统一下,需要使用nginx进行统一入口。

部署安装nginx

安装包在资料中:nginx-1.17.3.zip

安装在任意目录,通过命令:start nginx.exe 启动:

启加载配置文件命令:nginx.exe -s reload

、修改配置

修改conf目录下的nginx.conf文件:

server {
listen 80;
server_name localhost;

    #charset koi8-r;

    #access_log  logs/host.access.log  main;

    #location / {
    #    root   html;
    #    index  index.html index.htm;
    #}

    #error_page  404              /404.html;

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   html;
    }

    location /user {
        proxy_pass   http://127.0.0.1:18080;
    }

    location / {
        proxy_pass   http://127.0.0.1:18081;
    }

}

采用拦截器进行缓存命中

编写拦截器:RedisCacheInterceptor。

 if (!enable) {
            //未开启缓存
            return true;
        }

        String method = request.getMethod();
        if (!StringUtils.equalsAnyIgnoreCase(method, "GET")) {
            // 非GET的请求不进行缓存处理
            return true;
        }

        // 通过缓存做命中,查询redis,redisKey ?  组成:md5(请求的url + 请求参数)
        String redisKey = createRedisKey(request);
        String data = this.redisTemplate.opsForValue().get(redisKey);
        if (StringUtils.isEmpty(data)) {
            // 缓存未命中
            return true;
        }

注册拦截器到Spring容器:

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private RedisCacheInterceptor redisCacheInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(this.redisCacheInterceptor).addPathPatterns("/**");
    }
}

包装request对象

由于在拦截器中读取了输入流的数据,在request中的输入流只能读取一次,请求进去Controller时,输入流中已经没有数据了,导致获取不到数据。

编写HttpServletRequest的包装类:

通过过滤器进行包装request对象:

响应结果写入到缓存

前面已经完成了缓存命中的逻辑,那么在查询到数据后,如果将结果写入到缓存呢?

通过ResponseBodyAdvice进行实现。

ResponseBodyAdvice是Spring提供的高级用法,会在结果被处理前进行拦截,拦截的逻辑自己实现,这样就可以
实现拿到结果数据进行写入缓存的操作了。

// 考虑到post请求是写入数据操作,所以就不进行缓存了,只针对get进行处理
return returnType.hasMethodAnnotation(GetMapping.class);
// return returnType.hasMethodAnnotation(GetMapping.class) || returnType.hasMethodAnnotation(PostMapping.class);

圈子功能

功能说明

探花交友项目中的圈子功能,类似微信的朋友圈,基本的功能为:发布动态、浏览好友动态、浏览推荐动态、点赞、评论、喜欢等功能。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4qYuNqSs-1619110228810)(探花交友.assets/image-20210406215610860.png)]

流程说明:

  • 用户发布动态,首先将动态内容写入到发布表。
  • 然后,将发布的指向写入到自己的相册表中。
  • 最后,将发布的指向写入到好友的时间线中。

表结构设计

发布表:

表名:quanzi_publish

{
    "id":1,#主键id
    "userId":1, #用户id
    "text":"今天心情很好", #文本内容
    "medias":"http://xxxx/x/y/z.jpg", #媒体数据,图片或小视频 url
    "seeType":1, #谁可以看,1-公开,2-私密,3-部分可见,4-不给谁看
    "seeList":[1,2,3], #部分可见的列表
    "notSeeList":[4,5,6],#不给谁看的列表
	"longitude":108.840974298098,#经度
	"latitude":34.2789316522934,#纬度
    "locationName":"上海市浦东区", #位置名称
    "created",1568012791171 #发布时间
}

相册表:

#表名:quanzi_album_{userId}
{
    "id":1,#主键id
    "publishId":1001, #发布id
    "created":1568012791171 #发布时间
}

时间线表:

#表名:quanzi_time_line_{userId}
{
    "id":1,#主键id,
    "userId":2, #好友id
    "publishId":1001, #发布id
    "date":1568012791171 #发布时间
}

评论表:

#表名:quanzi_comment
{
    "id":1, #主键id
    "publishId":1001, #发布id
    "commentType":1, #评论类型,1-点赞,2-评论,3-喜欢
    "content":"给力!", #评论内容
    "userId":2, #评论人
    "isParent":false, #是否为父节点,默认是否
    "parentId":1001, #父节点id
    "created":1568012791171
}
、解决问题

了解完MongoDB的集群方案后,为了实现海量数据存储的需求,我们应该选择分片式集群,下面我们探讨下圈子的表设计。

  • 发布表(quanzi_publish)
    • 建议选择userId作为片键。
  • 评论表(quanzi_comment)
    • 建议选择publishId作为片键。
  • 相册表(quanzi_album_{userId})
    • 由于MongoDB的分片是集群集合的,所以需要将相册表的数据写入到一个集合中,按照userId进行分片。(增加userId字段)
  • 时间线表(quanzi_time_line_{userId})
    • 与相册相同,需要将数据写入到一个集合,按照my_userId进行分片。(增加my_userId字段)
 // 校验
        if (publish.getUserId() == null) {
            return false;
        }

        try {
            publish.setCreated(System.currentTimeMillis()); //设置创建时间
            publish.setId(ObjectId.get()); //设置id
            this.mongoTemplate.save(publish); //保存发布

            Album album = new Album(); // 构建相册对象
            album.setPublishId(publish.getId());
            album.setCreated(System.currentTimeMillis());
            album.setId(ObjectId.get());
            this.mongoTemplate.save(album, "quanzi_album_" + publish.getUserId());

            //写入好友的时间线中
            Criteria criteria = Criteria.where("userId").is(publish.getUserId());
            List<Users> users = this.mongoTemplate.find(Query.query(criteria), Users.class);
            for (Users user : users) {
                TimeLine timeLine = new TimeLine();
                timeLine.setId(ObjectId.get());
                timeLine.setPublishId(publish.getId());
                timeLine.setUserId(user.getUserId());
                timeLine.setDate(System.currentTimeMillis());
                this.mongoTemplate.save(timeLine, "quanzi_time_line_" + user.getFriendId());
            }

            return true;
        } catch (Exception e) {
            e.printStackTrace();
            //TODO 出错的事务回滚,MongoDB非集群不支持事务,暂不进行实现

统一处理token

在之前的开发中,我们会在每一个Service中对token做处理,相同的逻辑一定是要进行统一处理的,接下来我们将使用拦截器+ThreadLocal的方式进行解决。

编写UserThreadLocal

package com.tanhua.server.utils;

import com.tanhua.server.pojo.User;

public class UserThreadLocal {

    private static final ThreadLocal<User> LOCAL = new ThreadLocal<User>();

    private UserThreadLocal() {

    }

    public static void set(User user) {
        LOCAL.set(user);
    }

    public static User get() {
        return LOCAL.get();
    }

}

编写TokenInterceptor

**
 * 统一完成根据token查询用User的功能
 */
@Component
public class TokenInterceptor implements HandlerInterceptor {

    @Autowired
    private UserService userService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            NoAuthorization noAnnotation = handlerMethod.getMethod().getAnnotation(NoAuthorization.class);
            if (noAnnotation != null) {
                // 如果该方法被标记为无需验证token,直接返回即可
                return true;
            }
        }

        String token = request.getHeader("Authorization");
        if (StringUtils.isNotEmpty(token)) {
            User user = this.userService.queryUserByToken(token);
            if (null != user) {
                UserThreadLocal.set(user); //将当前对象,存储到当前的线程中
                return true;
            }
        }

        //请求头中如不存在Authorization直接返回false
        response.setStatus(401); //无权限访问
        return false;
    }
}

编写注解NoAuthorization

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented //标记注解
public @interface NoAuthorization {

}

注册拦截器

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private RedisCacheInterceptor redisCacheInterceptor;
    @Autowired
    private TokenInterceptor tokenInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注意拦截器的顺序
        registry.addInterceptor(this.tokenInterceptor).addPathPatterns("/**");
        registry.addInterceptor(this.redisCacheInterceptor).addPathPatterns("/**");
    }
}

使用ThreadLocal

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VLeFR5kp-1619110228813)(探花交友.assets/image-20210406220327859.png)]

  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
Java探花交友资料下载,是指通过使用Java编程语言,实现对探花交友网站的用户资料下载功能。 首先,我们需要了解探花交友网站的API接口及其文档,以便于编写Java程序进行数据的获取和处理。根据API文档,我们可以通过发送HTTP请求来获取用户资料的接口地址,并通过Java代码实现发送请求并接收响应数据。 在Java中,我们可以使用如下代码示例来实现用户资料下载功能: ``` import java.io.*; import java.net.*; public class UserProfileDownload { public static void main(String[] args) { String apiUrl = "https://api.example.com/user/profile"; // 探花交友API接口地址 String userId = "123456"; // 要下载的用户ID try { URL url = new URL(apiUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setRequestProperty("User-Agent", "Mozilla/5.0"); int responseCode = conn.getResponseCode(); if (responseCode == HttpURLConnection.HTTP_OK) { BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream())); String line; StringBuilder response = new StringBuilder(); while ((line = br.readLine()) != null) { response.append(line); } br.close(); // 对获取到的用户资料数据进行处理 String userProfile = response.toString(); // TODO: 进行用户资料的下载或者其他处理 System.out.println("用户资料下载成功!"); } else { System.out.println("用户资料下载失败,错误代码:" + responseCode); } } catch (Exception e) { e.printStackTrace(); } } } ``` 以上代码中,我们通过创建一个URL对象,并打开连接 (openConnection)。随后,我们设置HTTP请求方法为GET,并设置User-Agent字段以模拟浏览器发送请求。 然后,我们通过读取响应体中的数据,将其存储到一个StringBuilder对象中。最后,我们可以根据需要对用户资料数据进行处理,比如进行下载操作或者其他的数据分析和展示。 需要注意的是,上述代码仅为示例,实际开发中可能还需要考虑处理异常、身份验证以及对API响应进行解析等问题。 综上所述,通过使用Java编程语言开发的代码,我们可以实现对探花交友网站的用户资料下载功能。通过发送HTTP请求并获取响应数据,我们可以对用户资料进行处理,以满足不同的需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值