# 第九章 app端基本功能展示
目标
- 能够完成app端文章列表展示功能开发
- 能够完成app端文章详情的功能开发
- 能够掌握解决long类型丢失精度的问题
- 能够完成app端登录的功能
- 能够掌握关注作者功能
1 app端-文章列表
1.1 需求分析
在手机端可以查看文章信息
分析
1. 默认的情况下 查询 10条 在默认的频道(就是没有频道)查询出 按照发布时间倒序排列的文章数据
2. 当往下滑动时,则重新获取最新的数据,即在手机顶部
3. 当往上滑动时,则获取更多的数据
实际上:就是根据条件分页查询而已,当下滑时,重新获取第一页的数据进行展示 ,上滑动时,就是获取下一页的数据
下滑:
上滑:
1.2功能实现
1.2.1 实现思路分析
前端发送请求 并传递参数:包括 分页的当前页码 和 每页显示的行 以及 频道的ID
后台接收请求和参数并执行分页查询 返回结果即可
需要涉及到以上两个表 需要关联表查询
1.2.1 功能实现操作
实际上咱们之前已经实现了分页查询了,不需要再实现了,但是之前的实现功能不能满足我们的需求,所以我们重新进行构建。
(1)创建controller
//有条件的 分页 排序查询
@PostMapping("/searchOrder")
public Result<PageInfo<ApArticle>> searchOrder(@RequestBody PageRequestDto<ApArticle> pageRequestDto){
//1.获取当前页码 和 每页显示的行
if (pageRequestDto.getSize()>10) {
//设置最多为10
pageRequestDto.setSize(10L);
}
if (pageRequestDto.getPage()<=0) {
//设置为第一个页
pageRequestDto.setPage(1L);
}
//3.执行分页查询
PageInfo<ApArticle> pageInfo = apArticleService.pageByOrder(pageRequestDto);
return Result.ok(pageInfo);
}
(2)创建service及实现类
@Override
public PageInfo<ApArticle> pageByOrder(PageRequestDto<ApArticle> pageRequestDto) {
Long page = pageRequestDto.getPage();
Long size = pageRequestDto.getSize();
ApArticle body = pageRequestDto.getBody();
//如果为空 则创建 并赋值为0 标识查询所有频道列表信息
if(body!=null && body.getChannelId()!=null){
}else{
body = new ApArticle();
//查询所有频道
body.setChannelId(0);
}
Long start = (page-1)*size;
List<ApArticle> apArticleList = apArticleMapper.pageByOrder(start,size,body.getChannelId());
Long total = apArticleMapper.selectArticleCount(body.getChannelId());
Long totalPages = total/size;
if(total%size>0){
totalPages++;
}
return new PageInfo<ApArticle>(page,size,total,totalPages,apArticleList);
}
(3)创建mapper
public interface ApArticleMapper extends BaseMapper<ApArticle> {
List<ApArticle> pageByOrder(@Param(value="start") Long start, @Param(value="size")Long size, @Param(value="channelId")Integer channelId);
Long selectArticleCount(@Param(value="channelId")Integer channelId);
}
(4)创建文件
(5)添加SQL语句
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.itheima.article.mapper.ApArticleMapper">
<select id="selectArticleCount" resultType="java.lang.Long" parameterType="java.lang.Integer">
SELECT
count(*)
FROM
ap_article aa
LEFT JOIN ap_article_config ac ON aa.id = ac.article_id
<where>
AND ac.is_delete = 0
AND ac.is_down = 0
<if test="channelId!=null and channelId !=0">
AND aa.channel_id=#{channelId}
</if>
</where>
</select>
<select id="pageByOrder" resultType="com.itheima.article.pojo.ApArticle" parameterType="map">
SELECT
aa.*
FROM
ap_article aa
LEFT JOIN ap_article_config ac ON aa.id = ac.article_id
<where>
AND ac.is_delete = 0
AND ac.is_down = 0
<if test="channelId!=null and channelId !=0">
AND aa.channel_id=#{channelId}
</if>
</where>
ORDER BY
aa.publish_time DESC
LIMIT #{start},#{size}
</select>
</mapper>
测试:
2 App端网关搭建
(1)参考其他的网关,新建模块,名称:
(2)pom文件:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>itheima-leadnews-gateway</artifactId>
<groupId>com.itheima</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>itheima-leadnews-gateway-app</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.itheima</groupId>
<artifactId>itheima-leadnews-common</artifactId>
<version>1.0-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
(3)application.yml
spring:
profiles:
active: dev
---
server:
port: 6003
spring:
application:
name: leadnews-app-gateway
profiles: dev
cloud:
nacos:
server-addr: 192.168.211.136:8848
discovery:
server-addr: ${spring.cloud.nacos.server-addr}
gateway:
globalcors:
cors-configurations:
'[/**]': # 匹配所有请求
allowedOrigins: "*" #跨域处理 允许所有的域
allowedHeaders: "*"
allowedMethods: # 支持的方法
- GET
- POST
- PUT
- DELETE
routes:
# 文章微服务
- id: article
uri: lb://leadnews-article
predicates:
- Path=/article/**
filters:
- StripPrefix= 1
---
server:
port: 6003
spring:
application:
name: leadnews-app-gateway
profiles: test
cloud:
nacos:
server-addr: 192.168.211.136:8848
discovery:
server-addr: ${spring.cloud.nacos.server-addr}
gateway:
globalcors:
cors-configurations:
'[/**]': # 匹配所有请求
allowedOrigins: "*" #跨域处理 允许所有的域
allowedHeaders: "*"
allowedMethods: # 支持的方法
- GET
- POST
- PUT
- DELETE
routes:
# 文章微服务
- id: article
uri: lb://leadnews-article
predicates:
- Path=/article/**
filters:
- StripPrefix= 1
---
server:
port: 6003
spring:
application:
name: leadnews-app-gateway
profiles: pro
cloud:
nacos:
server-addr: 192.168.211.136:8848
discovery:
server-addr: ${spring.cloud.nacos.server-addr}
gateway:
globalcors:
cors-configurations:
'[/**]': # 匹配所有请求
allowedOrigins: "*" #跨域处理 允许所有的域
allowedHeaders: "*"
allowedMethods: # 支持的方法
- GET
- POST
- PUT
- DELETE
routes:
# 文章微服务
- id: article
uri: lb://leadnews-article
predicates:
- Path=/article/**
filters:
- StripPrefix= 1
(4)引导类:
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayAppApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayAppApplication.class,args);
}
}
4 app文章详情展示
4.1 app文章详情-需求分析
在文章列表中点击文章进入到文章详情查看页面,页面显示内容包括:标题、作者、作者头像、发布时间、是否关注、喜欢、不喜欢、分享、评论、收藏、转发、猜你喜欢、打赏等内容。除此之外还需收集用户打开页面时间、阅读次数、阅读百分比等信息。
4.2 app文章详情-思路分析
文章详情所展示的主要是文章的内容,这个时候无须再次加重ap_article表中的数据,只需要通过前端传递过来的文章id去查询文章的内容即可,同时需要判断当前文章是否已上架和是否已经删除。
由分析得出,主要是通过文章id查询ap_article_content和ap_article_config表的数据即可。
ap_article_content --> app文章内容表
ap_article_config --> app文章配置表
4.3 App文章详情-功能实现
根据文章的ID 获取到文章内容和文章的配置信息 合并之后 返回给前端即可。为此我们可以使用之前用过的一个dto如下:
前端获取到该POJO对应个各个属性中的值,即可
4.3.1 controller
@GetMapping("/detail/{articleId}")
public Result<ArticleInfoDto> detailByArticleId(@PathVariable(name="articleId") Long articleId){
ArticleInfoDto articleInfoDto = apArticleService.detailByArticleId(articleId);
return Result.ok(articleInfoDto);
}
4.3.2 service
@Override
public ArticleInfoDto detailByArticleId(Long articleId) {
//1.根据articleId 获取文章详情内容
QueryWrapper<ApArticleContent> queryWrapper1 = new QueryWrapper();
queryWrapper1.eq("article_id",articleId);
ApArticleContent articleContent = apArticleContentMapper.selectOne(queryWrapper1);
//2.获取articleId 获取文章配置信息
QueryWrapper<ApArticleConfig> queryWrapper2 = new QueryWrapper();
queryWrapper2.eq("article_id",articleId);
queryWrapper2.eq("is_down",0);
queryWrapper2.eq("is_delete",0);
ApArticleConfig apArticleConfig = apArticleConfigMapper.selectOne(queryWrapper2);
//3.合并返回
ArticleInfoDto articleInfoDto = new ArticleInfoDto();
articleInfoDto.setApArticleContent(articleContent);
articleInfoDto.setApArticleConfig(apArticleConfig);
return articleInfoDto;
}
4.3.5 测试
目前文章id为long类型,在转换json传递到前端以后精度丢失,POSTMAN测试是可以的,所以需要将数据转换成字符串返回给前端即可。也就是在后台返回给前端的时候 默认的情况下是由JACKSON进行转换成JSON的,但是数据还是Long类型所以给前端变成了丢失精度。
4.4 Long类型转换精度丢失问题解决
4.4.1 解决方案分析:
在后台返回给前端的时候 默认的情况下是由JACKSON进行转换成JSON的,但是数据还是Long类型所以给前端变成了丢失精度。只要在后台返回给前端的时候数据为字符串就可以解决这个问题了。
由此我们可以采用的方式有多种方式:
1. 全局设置 全局设置 之后 所有的返回给前端都是返回了字符串
2. 局部设置一个序列化器 在哪里使用哪里添加即可
那么我们可以采用第二种 使用自定义序列化器,当某一个字段需要使用到的时候,可以通过注解进行定义即可。jackson已经提供了现有的注解,可以直接使用。
@JsonSerialize(using = 你自定义的序列化器.class)
private Long articleId;
第一种:大家可以参考如下
package com.itheima.common.jackson;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
@Configuration
public class JacksonConfig {
/**
* Jackson全局转化long类型为String,解决jackson序列化时传入前端Long类型缺失精度问题
*/
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
Jackson2ObjectMapperBuilderCustomizer cunstomizer = new Jackson2ObjectMapperBuilderCustomizer() {
@Override
public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
//变成字符串
jacksonObjectMapperBuilder.serializerByType(Long.class, ToStringSerializer.instance);
}
};
return cunstomizer;
}
}
4.4.2 实现操作
(1)定义序列化器
package com.itheima.common.util;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
/**
* 自定义序列化器 将Long类型数据转成String
*/
public class Long2StringSerializer extends JsonSerializer<Long> {
@Override
public void serialize(Long value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if(value!=null){
gen.writeString(value.toString());
}
}
}
(2)在字段中进行使用
如果其他的地方也需要,则可以根据需要添加注解即可。
测试:
先进过controller 再经过转换器
5 app端登录功能
5.1 app端登录-需求分析
- 点击登录可以根据app端的手机号和密码进行登录
- 点击不登录,先看看可以在无登录状态下进入app
app前端项目启动完成后,直接点击不登录,先看看即可,就可以直接跳转到文章列表首页:
5.2 app端登录-思路分析
概念介绍:用户设备,即当前用户所使用的终端设备。 在进行登录或者不登录看一看操作的时候 可以传递给后台,后台接收之后做一些业务处理。这个后面再说。对应的dto有一个设备的ID 需要注意下。
1,用户点击登录
- 根据标记值为1 时,并且用户输入手机号和密码到后端进行校验,校验成功生成token返给前端
- 其他请求需要带着token到app网关去校验jwt,校验成功,放行
2,用户点击不登录,先看看
- 标记值为0时,生成固定的Token 设置jwt存储的id为0
- 其他请求需要带着token到app网关去校验jwt,校验成功,放行
5.3 app端登录-刷新令牌-功能实现
5.3.1 需求分析
跟之前的网关的功能实现差不多
先经过网关 判断 如果是有令牌 则放行,如果是没有令牌 则去登录
登录完成之后返回令牌 再次登录即可。
5.3.2 创建dto
@Data
@Getter
@Setter
public class LoginDto {
//设备id
private Integer equipmentId;
//0 表示 不登录先看看 1表示 需要登录 默认为 1
private Integer flag = 1;
//手机号
private String phone;
//密码
private String password;
}
5.3.3 controller
@RestController
@RequestMapping("/app")
public class LoginController {
@Autowired
private ApUserService apUserService;
/**
* 传递刷新令牌值 和 用户ID
*
* @param map
* @return
*/
//刷新令牌
@PostMapping("/refreshToken")
public Result refreshToken(@RequestBody Map<String, String> map) {
String refreshToken = map.get("refreshToken");
UserTokenInfoExp userTokenInfoExp = null;
try {
userTokenInfoExp = JwtUtil.parseJwtUserToken(refreshToken);
Long exp = userTokenInfoExp.getExp();
long now = System.currentTimeMillis();
long chazhi = exp - now;
//续约的要求是: 必须在 访问令牌的过期时间点 到 刷新令牌的过期时间点 之间 防止 出现过久的令牌来恶意刷新令牌
if(chazhi>(JwtUtil.TOKEN_TIME_OUT*1000)){
return Result.errorMessage("令牌续约时间不在有效范围之内");
}
} catch (Exception e) {
return Result.errorMessage("令牌错误");
}
if (JwtUtil.isExpire(userTokenInfoExp)) {
return Result.errorMessage("令牌错误");
}
//重新生成
TokenJsonVo token = JwtUtil.createToken(userTokenInfoExp);
return Result.ok(token);
}
@PostMapping("/login")
public Result login(@RequestBody LoginDto appUser) throws LeadNewsException{
//不登录先看看
if (appUser.getFlag() == 0) {
UserTokenInfo anonymousInfo = UserTokenInfo.getAnonymous();
TokenJsonVo token = JwtUtil.createToken(anonymousInfo);
return Result.ok(token);
} else {
//要登录
if (StringUtils.isEmpty(appUser.getPhone()) || StringUtils.isEmpty(appUser.getPassword())) {
return Result.errorMessage("用户名或密码不能为空");
}
//校验用户名和密码
TokenJsonVo token = apUserService.login(appUser);
return Result.ok(token);
}
}
}
5.3.4 service
接口:
public interface ApUserService extends IService<ApUser> {
TokenJsonVo login(LoginDto appUser) throws LeadNewsException;
}
接口对应实现类
@Service
public class ApUserServiceImpl extends ServiceImpl<ApUserMapper, ApUser> implements ApUserService {
@Autowired
private ApUserMapper apUserMapper;
@Override
public TokenJsonVo login(LoginDto appUser) throws LeadNewsException {
//根据用户名获取到用户信息
QueryWrapper<ApUser> querywrapper = new QueryWrapper<ApUser>();
querywrapper.eq("phone", appUser.getPhone());
//获取到数据库中的用户
ApUser user = apUserMapper.selectOne(querywrapper);
//再获取用户表中的盐值
if (user == null) {
throw new LeadNewsException("手机号或密码错误");
}
String salt = user.getSalt();
String password = DigestUtils.md5DigestAsHex((appUser.getPassword() + salt).getBytes());
//传递过来的密码和数据库中的密码进行匹配
if (!password.equals(user.getPassword())) {
throw new LeadNewsException("手机号或密码错误");
}
UserTokenInfo userTokenInfo = new UserTokenInfo(
Long.valueOf(user.getId()),
user.getImage(),
user.getName(),
user.getName(),
TokenRole.ROLE_APP);
TokenJsonVo token = JwtUtil.createToken(userTokenInfo);
return token;
}
}
5.3.5 网关配置及校验
(1)参考其他的网关系统创建过滤器实现业务逻辑.
package com.itheima.gatewayapp;
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {
//获取用户携带的token令牌 解析校验 校验通过放行 不通过 返回错误
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1.获取请求对象 和 响应对象
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
//1.5 如果请求的路径是 登录【白名单】 放行即可
String path = request.getURI().getPath();
if (path.startsWith("/user/app/login") || path.startsWith("/user/app/refreshToken") || path.endsWith("v2/api-docs")) {
return chain.filter(exchange);
}
//2.从请求头中获取令牌数据
String accessToken = request.getHeaders().getFirst("token");
//3.判断 是否为空 如果为空 返回错误 401
if (StringUtils.isEmpty(accessToken)) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
try {
UserTokenInfoExp userTokenInfoExp = JwtUtil.parseJwtUserToken(accessToken);
if (!JwtUtil.isValidRole(userTokenInfoExp, TokenRole.ROLE_APP)) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
//如果过期
if (JwtUtil.isExpire(userTokenInfoExp)) {
response.setStatusCode(HttpStatus.FORBIDDEN);//表示要刷新令牌
return response.setComplete();
}
//URL编码 否则有乱码产生
String encode = URLEncoder.encode(JSON.toJSONString(userTokenInfoExp), "UTF-8");
//将信息传递给下游微服务
request.mutate().header(SystemConstants.USER_HEADER_NAME, encode);
} catch (Exception e) {
//校验没通过
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
return chain.filter(exchange);
}
//值越低 优先级越高 优先被执行
@Override
public int getOrder() {
return -10;
}
}
(2)配置yaml 路由到user微服务
spring:
profiles:
active: dev
---
server:
port: 6003
spring:
application:
name: leadnews-app-gateway
profiles: dev
cloud:
nacos:
server-addr: 192.168.211.136:8848
discovery:
server-addr: ${spring.cloud.nacos.server-addr}
gateway:
globalcors:
cors-configurations:
'[/**]': # 匹配所有请求
allowedOrigins: "*" #跨域处理 允许所有的域
allowedHeaders: "*"
allowedMethods: # 支持的方法
- GET
- POST
- PUT
- DELETE
routes:
# 文章微服务
- id: article
uri: lb://leadnews-article
predicates:
- Path=/article/**
filters:
- StripPrefix= 1
# app用户微服务
- id: user
uri: lb://leadnews-user
predicates:
- Path=/user/**
filters:
- StripPrefix= 1
---
server:
port: 6003
spring:
application:
name: leadnews-app-gateway
profiles: test
cloud:
nacos:
server-addr: 192.168.211.136:8848
discovery:
server-addr: ${spring.cloud.nacos.server-addr}
gateway:
globalcors:
cors-configurations:
'[/**]': # 匹配所有请求
allowedOrigins: "*" #跨域处理 允许所有的域
allowedHeaders: "*"
allowedMethods: # 支持的方法
- GET
- POST
- PUT
- DELETE
routes:
# 文章微服务
- id: article
uri: lb://leadnews-article
predicates:
- Path=/article/**
filters:
- StripPrefix= 1
# app用户微服务
- id: user
uri: lb://leadnews-user
predicates:
- Path=/user/**
filters:
- StripPrefix= 1
---
server:
port: 6003
spring:
application:
name: leadnews-app-gateway
profiles: pro
cloud:
nacos:
server-addr: 192.168.211.136:8848
discovery:
server-addr: ${spring.cloud.nacos.server-addr}
gateway:
globalcors:
cors-configurations:
'[/**]': # 匹配所有请求
allowedOrigins: "*" #跨域处理 允许所有的域
allowedHeaders: "*"
allowedMethods: # 支持的方法
- GET
- POST
- PUT
- DELETE
routes:
# 文章微服务
- id: article
uri: lb://leadnews-article
predicates:
- Path=/article/**
filters:
- StripPrefix= 1
# app用户微服务
- id: user
uri: lb://leadnews-user
predicates:
- Path=/user/**
filters:
- StripPrefix= 1
测试:如果设置为0 则登录为匿名用户
否则为真实用户:
6 app端-关注作者或取消关注
6.1 需求分析
如上效果:
当前登录后的用户可以关注作者,也可以取消关注作者
6.2 思路分析
一个用户关注了作者,作者是由用户实名认证以后开通的作者权限,才有了作者信息,作者肯定是app中的一个用户。
从用户的角度出发:一个用户可以关注其他多个作者
从作者的角度出发:一个用户(同是作者)也可以拥有很多个粉丝
实现步骤:
1 前端传递作者id获取作者信息,最终获取中作者在当前app端的账号id
2 如果是关注操作,需要保存数据,用户保存关注的作者,作者保存当前的粉丝
2.1 异步记录关注行为(后面开发,为了推荐做准备)
3 如果是取消关注,删除用户关注的作者,删除作者当前的粉丝
流程分析:
-
app端关注信息表
记录了当前登录用户和关注人(作者)的关系,方便当前用户查看关注的作者
-
app端用户粉丝表
记录了作者与粉丝的关系,方便作者查看自己的粉丝,同时当前作者也是app中的一个用户
- app用户表与app作者表是一对一关系,只有在用户认证以后才会有作者出现
- app用户表与app用户关注表是一对多的关系,一个用户可以关注多个作者
- app用户表与app用户粉丝表是一对多的关系,一个用户可以拥有多个粉丝
ap_user_follow APP用户关注信息表
ap_user_fan APP用户粉丝信息表
6.3 功能实现
6.3.1 需求分析
需求是需要实现两个功能:
1. app用户登录之后 关注某一个作者(该作者 也一定是APP用户)
2. app用户登录之后 可以取消关注一个作者(该作者 也一定是APP用户)
关注也好 取消关注也好 都要对 粉丝信息表 和 关注表 进行保存和删除关系。
步骤:
(1)定义DTO 设置需要传递给后台的参数属性
(2)编写controller service mapper 实现相关的业务逻辑即可 注意需要判断权限
6.3.2 功能实现
(1)创建dto
@Data
@Setter
@Getter
public class UserRelationDto {
// 文章作者ID
Integer authorId;
//作者名称
String authorName;
// 文章id
Long articleId;
/**
* 操作方式
* 1 关注
* 0 取消
*/
Integer operation;
}
(2)添加一个枚举类型
添加构造函数,如果有就不用添加了:如下
(3)再入下图位置修改 controller
//当前登录用户关注某一个作者 或者取消某一个作者
@PostMapping("/follow")
public Result follow(@RequestBody UserRelationDto relationDto) throws LeadNewsException {
apUserFollowService.follow(relationDto);
//调用一个接口(数据放到大数据框架存储)
return Result.ok();
}
(4)修改service
public interface ApUserFollowService extends IService<ApUserFollow> {
//取消关注和关注
void follow(UserRelationDto relationDto) throws Exception;
}
(5)修改service实现类
实现步骤如下:
1.判断条件检查参数
2.获取操作类型 要关注还是取消关注
3.根据文章ID 获取作者信息 并获取作者对应的APP userid的值
4.判断是否为关注 还是取消关注 如果是关注
4.1先查询是否已有关注表记录
4.2再查询是否有粉丝表记录
4.3进行数据封装 最终存储到数据库表中即可
5.如果是取消关注
@Service
public class ApUserFollowServiceImpl extends ServiceImpl<ApUserFollowMapper, ApUserFollow> implements ApUserFollowService {
@Autowired
private ApUserFollowMapper apUserFollowMapper;
@Autowired
private ApUserFanMapper apUserFanMapper;
@Autowired
private ApAuthorFeign apAuthorFeign;
@Override
public void follow(UserRelationDto relationDto) throws LeadNewsException {
//判断当前用户是否是匿名用户
if (RequestContextUtil.isAnonymous()) {
throw new LeadNewsException(StatusCode.NEED_LOGIN.code(), StatusCode.NEED_LOGIN.message());
}
Integer currentUserId = RequestContextUtil.getUserId();
//先根据页面传递过来的作者的ID 获取到作者的信息(user_id:作者对应的app_user_id,name,id)
ApAuthor apAuthor = apAuthorFeign.findById(relationDto.getAuthorId()).getData();
if (apAuthor == null) {
throw new LeadNewsException("错误");
}
if (relationDto.getOperation() == 1) {
//如果是关注 就添加关注记录
// 需要先查询 是否有记录 有记录不能添加记录
QueryWrapper<ApUserFollow> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_id", currentUserId);
queryWrapper.eq("follow_id", apAuthor.getUserId());
ApUserFollow apUserFollow1 = apUserFollowMapper.selectOne(queryWrapper);
if (apUserFollow1 != null) {
throw new LeadNewsException("已经关注过了");
}
// 张三 关注了 李四
ApUserFollow apUserFollow = new ApUserFollow();
//张三
apUserFollow.setUserId(currentUserId);
//李四
apUserFollow.setFollowId(apAuthor.getUserId());
apUserFollow.setFollowName(apAuthor.getName());
apUserFollow.setLevel(1);//普通的关注
apUserFollow.setIsNotice(1);
apUserFollow.setCreatedTime(LocalDateTime.now());
apUserFollowMapper.insert(apUserFollow);
//添加粉丝记录
ApUserFan apUserFan = new ApUserFan();
//张三
apUserFan.setFansId(currentUserId);
apUserFan.setFansName(RequestContextUtil.getRequestUserTokenInfo().getName());
//李四
apUserFan.setUserId(apAuthor.getUserId());
apUserFan.setLevel(1);
apUserFan.setIsDisplay(0);
apUserFan.setIsShieldLetter(0);
apUserFan.setIsShieldComment(0);
apUserFan.setCreatedTime(LocalDateTime.now());
apUserFanMapper.insert(apUserFan);
} else {
//取消关注 删除记录
//删除 取消关注
apUserFollowMapper.deleteFollow(currentUserId, apAuthor.getUserId());
//删除 粉丝记录
apUserFanMapper.deleteFan(apAuthor.getUserId(), currentUserId);
}
}
}
mapper设置:
public interface ApUserFollowMapper extends BaseMapper<ApUserFollow> {
@Delete(value="delete from ap_user_follow where user_id=#{currentUserId} and follow_id=#{followId}")
void deleteFollow(@Param(value="currentUserId") Integer currentUserId, @Param(value="followId")Integer followId);
}
public interface ApUserFanMapper extends BaseMapper<ApUserFan> {
/**
* 张三关注了李四
* @param lisi
* @param zhangsan
*/
@Delete(value="delete from ap_user_fan where user_id=#{lisi} and fans_id=#{zhangsan}")
void deleteFan(@Param(value="lisi") Integer lisi, @Param(value="zhangsan") Integer zhangsan);
}
测试:先登录,再测试
使用该token 通过app网关进行关注或者取消关注。