一、登录功能
1、需求分析
2、表结构
3、加密方式(手动加密:md5+盐)
4、微服务结构
1.1、代码实现
①定义LoginDto
package com.heima.model.user.dtos;
import lombok.Data;
@Data
public class LoginDto {
/**
* 手机号
*/
private String phone;
/**
* 密码
*/
private String password;
}
②定义控制层controller
package com.heima.user.controller.v1;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.user.dtos.LoginDto;
import com.heima.user.service.ApUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/login")
public class ApUserLoginController {
@Autowired
private ApUserService apUserService;
@PostMapping("/login_auth")
public ResponseResult login(@RequestBody LoginDto dto){
return apUserService.login(dto);
}
}
③定义持久层mapper
package com.heima.user.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.heima.model.user.pojos.ApUser;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ApUserMapper extends BaseMapper<ApUser> {
}
④定义service
package com.heima.user.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.user.dtos.LoginDto;
import com.heima.model.user.pojos.ApUser;
public interface ApUserService extends IService<ApUser> {
/**
* app端登录功能
* @param dto
* @return
*/
public ResponseResult login(LoginDto dto);
}
实现类
package com.heima.user.service.impl;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.user.dtos.LoginDto;
import com.heima.model.user.pojos.ApUser;
import com.heima.user.mapper.ApUserMapper;
import com.heima.user.service.ApUserService;
import com.heima.utils.common.AppJwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
import java.util.HashMap;
import java.util.Map;
@Service
@Transactional
@Slf4j
public class ApUserServiceImpl extends ServiceImpl<ApUserMapper, ApUser> implements ApUserService {
/**
* app端登录功能
* @param dto
* @return
*/
@Override
public ResponseResult login(LoginDto dto) {
//1.正常登录 用户名和密码
if(StringUtils.isNoneBlank(dto.getPhone()) && StringUtils.isNoneBlank(dto.getPassword())){
//1.1 根据手机号查询用户信息
ApUser dbUser = getOne(Wrappers.<ApUser>lambdaQuery().eq(ApUser::getPhone, dto.getPhone()));
if(dbUser == null){
return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST,"用户信息不存在");
}
//1.2 比对密码
String salt = dbUser.getSalt();
String password = dto.getPassword();
String pswd = DigestUtils.md5DigestAsHex((password+salt).getBytes());
if(!pswd.equals(dbUser.getPassword())){
return ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_PASSWORD_ERROR);
}
//1.3 返回数据 jwt user
String token = AppJwtUtil.getToken(dbUser.getId().longValue());
Map<String,Object> map = new HashMap<>();
map.put("token",token);
dbUser.setSalt("");
dbUser.setPassword("");
map.put("user",dbUser);
return ResponseResult.okResult(map);
}else {
//2.游客登陆
Map<String,Object> map = new HashMap<>();
map.put("token",AppJwtUtil.getToken(0L));
return ResponseResult.okResult(map);
}
}
}
⑤postman测试
1.2、app端网关
通过网关进行路由,选择对应的微服务
项目中网关的服务工程结构
①在heima-leadnews-gateway导入以下依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
</dependencies>
②在heima-leadnews-gateway下创建heima-leadnews-app-gateway微服务
启动类
package com.heima.app.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient //开启注册中心
public class AppGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(AppGatewayApplication.class,args);
}
}
bootstrap.yml
server:
port: 51601
spring:
application:
name: leadnews-app-gateway
cloud:
nacos:
discovery:
server-addr: 192.168.200.130:8848
config:
server-addr: 192.168.200.130:8848
file-extension: yml
③在nacos的配置中心创建dataid为leadnews-app-gateway的yml配置
spring:
cloud:
gateway:
globalcors:
add-to-simple-url-handler-mapping: true
corsConfigurations:
'[/**]':
allowedHeaders: "*"
allowedOrigins: "*"
allowedMethods:
- GET
- POST
- DELETE
- PUT
- OPTION
routes:
# 平台管理
- id: user
uri: lb://leadnews-user
predicates:
- Path=/user/**
filters:
- StripPrefix= 1
环境搭建完成以后,启动项目网关和用户两个服务,使用postman进行测试
1.3、全局过滤器实现jwt校验
流程图
思路分析:
1. 用户进入网关开始登陆,网关过滤器进行判断,如果是登录,则路由到后台管理微服务进行登录
2. 用户登录成功,后台管理微服务签发JWT TOKEN信息返回给用户
3. 用户再次进入网关开始访问,网关过滤器接收用户携带的TOKEN
4. 网关过滤器解析TOKEN ,判断是否有权限,如果有,则放行,如果没有则返回未认证错误
再网关微服务中新建全局过滤器
package com.heima.app.gateway.filter;
import com.heima.app.gateway.util.AppJwtUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
@Slf4j
public class AuthorizeFilter implements Ordered, GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1.获取request和response
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
//2.判断是否是登录
if (request.getURI().getPath().contains("/login")) {
//放行
return chain.filter(exchange);
}
//3.获取token
String token = request.getHeaders().getFirst("token");
//4.判断token是否存在
if (StringUtils.isBlank(token)) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();//结束请求并返回给前端401
}
//5.判断token是否有效
try {
Claims claimsBody = AppJwtUtil.getClaimsBody(token);
//是否是过期
int result = AppJwtUtil.verifyToken(claimsBody); //-1:有效,0:有效,1:过期,2:过期
if (result == 1 || result == 2) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();//结束请求并返回给前端401
}
}catch (Exception e){
e.printStackTrace();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();//结束请求并返回给前端401
}
//6.放行
return chain.filter(exchange);
}
/**
* 设置优先级 值越小 优先级越高
*
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
1.4、app前端项目集成
通过nginx来进行配置,功能如下
- 通过nginx的反向代理功能访问后台的网关资源
- 通过nginx的静态服务器功能访问前端静态页面
二、app端文章列表
需求
文章表结构
为什么要拆分文章表
使用垂直分表的方式
实现思路及实现功能
2.1、文章列表功能实现
①导入heima-leadnews-article微服务。注意:需要在heima-leadnews-service的pom文件夹中添加子模块信息,如下:
在nacos中进行如下配置
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/leadnews_article?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: root
# 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置
mybatis-plus:
mapper-locations: classpath*:mapper/*.xml
# 设置别名包扫描路径,通过该属性可以给包中的类注册别名
type-aliases-package: com.heima.model.article.pojos
②定义控制层controller
package com.heima.article.controller.v1;
import com.heima.article.service.ApArticleService;
import com.heima.common.constants.ArticleConstants;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.common.dtos.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/article")
public class ArticleHomeController {
@Autowired
private ApArticleService apArticleService;
/**
* 加载首页
* @param dto
* @return
*/
@PostMapping("/load")
public ResponseResult load(@RequestBody ArticleHomeDto dto) {
return apArticleService.load(dto, ArticleConstants.LOADTYPE_LOAD_MORE);
}
/**
* 加载更多
* @param dto
* @return
*/
@PostMapping("/loadmore")
public ResponseResult loadMore(@RequestBody ArticleHomeDto dto) {
return apArticleService.load(dto, ArticleConstants.LOADTYPE_LOAD_MORE);
}
/**
* 加载最新
* @param dto
* @return
*/
@PostMapping("/loadnew")
public ResponseResult loadNew(@RequestBody ArticleHomeDto dto) {
return apArticleService.load(dto, ArticleConstants.LOADTYPE_LOAD_NEW);
}
}
③定义持久层mapper
package com.heima.article.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.article.pojos.ApArticle;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface ApArticleMapper extends BaseMapper<ApArticle> {
public List<ApArticle> loadArticleList(@Param("dto") ArticleHomeDto dto, @Param("type") Short type);
}
对应的映射文件,在resources中新建mapper/ApArticleMapper.xml
<?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.heima.article.mapper.ApArticleMapper">
<resultMap id="resultMap" type="com.heima.model.article.pojos.ApArticle">
<id column="id" property="id"/>
<result column="title" property="title"/>
<result column="author_id" property="authorId"/>
<result column="author_name" property="authorName"/>
<result column="channel_id" property="channelId"/>
<result column="channel_name" property="channelName"/>
<result column="layout" property="layout"/>
<result column="flag" property="flag"/>
<result column="images" property="images"/>
<result column="labels" property="labels"/>
<result column="likes" property="likes"/>
<result column="collection" property="collection"/>
<result column="comment" property="comment"/>
<result column="views" property="views"/>
<result column="province_id" property="provinceId"/>
<result column="city_id" property="cityId"/>
<result column="county_id" property="countyId"/>
<result column="created_time" property="createdTime"/>
<result column="publish_time" property="publishTime"/>
<result column="sync_status" property="syncStatus"/>
<result column="static_url" property="staticUrl"/>
</resultMap>
<select id="loadArticleList" resultMap="resultMap">
SELECT
aa.*
FROM
`ap_article` aa
LEFT JOIN ap_article_config aac ON aa.id = aac.article_id
<where>
and aac.is_delete != 1
and aac.is_down != 1
<!-- loadmore -->
<if test="type != null and type == 1">
and aa.publish_time <![CDATA[<]]> #{dto.minBehotTime}
</if>
<if test="type != null and type == 2">
and aa.publish_time <![CDATA[>]]> #{dto.maxBehotTime}
</if>
<if test="dto.tag != '__all__'">
and aa.channel_id = #{dto.tag}
</if>
</where>
order by aa.publish_time desc
limit #{dto.size}
</select>
</mapper>
④定义业务层service
package com.heima.article.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.common.dtos.ResponseResult;
public interface ApArticleService extends IService<ApArticle> {
/**
* 加载文章列表
* @param dto
* @param type 1 加载更多 2 加载最新
* @return
*/
public ResponseResult load(ArticleHomeDto dto,Short type);
}
实现类
package com.heima.article.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.article.service.ApArticleService;
import com.heima.common.constants.ArticleConstants;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.common.dtos.ResponseResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.List;
@Service
@Transactional
@Slf4j
public class ApArticleServiceImpl extends ServiceImpl<ApArticleMapper, ApArticle> implements ApArticleService {
@Autowired
private ApArticleMapper apArticleMapper;
private final static short MAX_PAGE_SIZE = 50;
/**
* 加载文章列表
*
* @param dto
* @param type 1 加载更多 2 加载最新
* @return
*/
@Override
public ResponseResult load(ArticleHomeDto dto, Short type) {
//1. 校验参数
//分页条数的校验
Integer size = dto.getSize();
if (size == null || size == 0) {
size = 10;
}
//分页的值不超过50
size = Math.min(size, MAX_PAGE_SIZE);
dto.setSize(size);
//校验参数type
if (!type.equals(ArticleConstants.LOADTYPE_LOAD_MORE) && !type.equals(ArticleConstants.LOADTYPE_LOAD_NEW)) {
type = ArticleConstants.LOADTYPE_LOAD_MORE;
}
//频道参数校验
if (StringUtils.isBlank(dto.getTag())) {
dto.setTag(ArticleConstants.DEFAULT_TAG);
}
//时间校验
if (dto.getMaxBehotTime() == null) dto.setMaxBehotTime(new Date());
if (dto.getMinBehotTime() == null) dto.setMinBehotTime(new Date());
//2.查询
List<ApArticle> articleList = apArticleMapper.loadArticleList(dto, type);
//3.返回结果
return ResponseResult.okResult(articleList);
}
}
⑤在app网关的微服务的nacos的配置中心添加文章微服务的路由
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]': # 匹配所有请求
allowedOrigins: "*" #跨域处理 允许所有的域
allowedMethods: # 支持的方法
- GET
- POST
- PUT
- DELETE
routes:
# 用户微服务
- id: user
uri: lb://leadnews-user
predicates:
- Path=/user/**
filters:
- StripPrefix= 1
# 文章微服务
- id: article
uri: lb://leadnews-article
predicates:
- Path=/article/**
filters:
- StripPrefix= 1
2.2、文章详情功能实现
需求分析
实现方案一
实现方案二
2.2.1、Freemarker
输出静态化文件
2.2.2、MinIO
快速入门,把list.html文件上传到minio中,并且可以在浏览器中访问
①创建工程,引入依赖
<?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>heima-leadnews-test</artifactId>
<groupId>com.heima</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>minio-demo</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>7.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
</project>
②创建启动类
package com.heima.minio;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MinIOApplication {
public static void main(String[] args) {
SpringApplication.run(MinIOApplication.class,args);
}
}
③创建测试类,上传html文件
package com.heima.minio.test;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import java.io.FileInputStream;
public class MinIOTest {
public static void main(String[] args) {
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream("D:\\list.html");;
//1.创建minio链接客户端
MinioClient minioClient = MinioClient.builder().credentials("minio", "minio123").endpoint("http://192.168.200.130:9000").build();
//2.上传
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.object("list.html")//文件名
.contentType("text/html")//文件类型
.bucket("leadnews")//桶名词 与minio创建的名词一致
.stream(fileInputStream, fileInputStream.available(), -1) //文件流
.build();
minioClient.putObject(putObjectArgs);
System.out.println("http://192.168.200.130:9000/leadnews/list.html");
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
封装MinIO为starter
2.2.3、静态模板展示
①在文章微服务中添加FreeMarker和Minio依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>com.heima</groupId>
<artifactId>heima-file-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
②新建ApArticleContentMapper
package com.heima.article.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.heima.model.article.pojos.ApArticleContent;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ApArticleContentMapper extends BaseMapper<ApArticleContent> {
}
③在artile微服务中新增测试类(后期新增文章的时候创建详情静态页,目前暂时手动生成)
package com.heima.article.test;
import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.heima.article.ArticleApplication;
import com.heima.article.mapper.ApArticleContentMapper;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.file.service.FileStorageService;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.article.pojos.ApArticleContent;
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.apache.commons.lang3.StringUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;
@SpringBootTest(classes = ArticleApplication.class)
@RunWith(SpringRunner.class)
public class ArticleFreemarkerTest {
@Autowired
private Configuration configuration;
@Autowired
private FileStorageService fileStorageService;
@Autowired
private ApArticleMapper apArticleMapper;
@Autowired
private ApArticleContentMapper apArticleContentMapper;
@Test
public void createStaticUrlTest() throws Exception {
//1.获取文章内容
ApArticleContent apArticleContent = apArticleContentMapper.selectOne(Wrappers.<ApArticleContent>lambdaQuery().eq(ApArticleContent::getArticleId, 1390536764510310401L));
if(apArticleContent != null && StringUtils.isNotBlank(apArticleContent.getContent())){
//2.文章内容通过freemarker生成html文件
StringWriter out = new StringWriter();
Template template = configuration.getTemplate("article.ftl");
Map<String, Object> params = new HashMap<>();
params.put("content", JSONArray.parseArray(apArticleContent.getContent()));
template.process(params, out);
InputStream is = new ByteArrayInputStream(out.toString().getBytes());
//3.把html文件上传到minio中
String path = fileStorageService.uploadHtmlFile("", apArticleContent.getArticleId() + ".html", is);
//4.修改ap_article表,保存static_url字段
ApArticle article = new ApArticle();
article.setId(apArticleContent.getArticleId());
article.setStaticUrl(path);
apArticleMapper.updateById(article);
}
}
}
三、自媒体文章发布
功能
自媒体端环境搭建
前端
3.1、自媒体素材管理
功能
表结构
问题
1.在素材表中的用户信息如何得到? user_id
2.素材的信息保存到什么位置?
3.1.1、素材表中的用户信息如何得到问题解决思路
①:网关进行token解析后,把解析后的用户信息存储到header中
package com.heima.wemedia.gateway.filter;
import com.heima.wemedia.gateway.util.AppJwtUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
@Slf4j
public class AuthorizeFilter implements Ordered, GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1.获取request和response对象
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
//2.判断是否是登录
if (request.getURI().getPath().contains("/login")) {
//放行
return chain.filter(exchange);
}
//3.获取token
String token = request.getHeaders().getFirst("token");
//4.判断token是否存在
if (StringUtils.isBlank(token)) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
//5.判断token是否有效
try {
Claims claimsBody = AppJwtUtil.getClaimsBody(token);
//是否是过期
int result = AppJwtUtil.verifyToken(claimsBody);
if (result == 1 || result == 2) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
//———————————————————————————————————————————————————————————————————————————————————————
//解决思路
//获取用户信息
Object userId = claimsBody.get("id");
//存入header
ServerHttpRequest serverHttpRequest = request.mutate().headers(httpHeaders -> {
httpHeaders.add("userId", userId + "");
}).build();
//重置请求
exchange.mutate().request(serverHttpRequest);
//———————————————————————————————————————————————————————————————————————————————————————
} catch (Exception e) {
e.printStackTrace();
}
//6.放行
return chain.filter(exchange);
}
/**
* 优先级设置 值越小 优先级越高
*
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
②自媒体微服务使用拦截器获取到header中的的用户信息,并放入到threadlocal中
ThreadLocal
package com.heima.utils.thread;
import com.heima.model.wemedia.pojos.WmUser;
public class WmThreadLocalUtil {
private final static ThreadLocal<WmUser> WM_USER_THREAD_LOCAL = new ThreadLocal<>();
/**
* 添加用户
* @param wmUser
*/
public static void setUser(WmUser wmUser){
WM_USER_THREAD_LOCAL.set(wmUser);
}
/**
* 获取用户
*/
public static WmUser getUser(){
return WM_USER_THREAD_LOCAL.get();
}
/**
* 清理用户
*/
public static void clear(){
WM_USER_THREAD_LOCAL.remove();
}
}
拦截器
package com.heima.wemedia.interceptor;
import com.heima.model.wemedia.pojos.WmUser;
import com.heima.utils.thread.WmThreadLocalUtil;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class WmTokenInterceptor implements HandlerInterceptor {
/**
* 得到header信息并存入当前线程中
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String userId = request.getHeader("userId");
if (userId!=null) {
//存入当前线程中
WmUser wmUser = new WmUser();
wmUser.setId(Integer.valueOf(userId));
WmThreadLocalUtil.setUser(wmUser);
}
return true;
}
/**
* 清理线程中的数据
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
WmThreadLocalUtil.clear();
}
}
配置拦截器生效
package com.heima.wemedia.config;
import com.heima.wemedia.interceptor.WmTokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new WmTokenInterceptor()).addPathPatterns("/**");
}
}
3.1.2、图片上传功能
①在自媒体导入heima-file-starter(MinIO)
<dependencies>
<dependency>
<groupId>com.heima</groupId>
<artifactId>heima-file-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
②在自媒体微服务的nacos配置中心添加以下配置:
minio:
accessKey: minio
secretKey: minio123
bucket: leadnews
endpoint: http://192.168.200.130:9000
readPath: http://192.168.200.130:9000
③创建WmMaterialController
package com.heima.wemedia.controller.v1;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.wemedia.service.WmMaterialService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@RestController
@RequestMapping("/api/v1/material")
public class WmMaterialController {
@Autowired
private WmMaterialService wmMaterialService;
@PostMapping("/upload_picture")
public ResponseResult uploadPicture(MultipartFile multipartFile) {
return wmMaterialService.uploadPicture(multipartFile);
}
}
④ 创建mapper
package com.heima.wemedia.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.heima.model.wemedia.pojos.WmMaterial;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface WmMaterialMapper extends BaseMapper<WmMaterial> {
}
⑤service
package com.heima.wemedia.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.wemedia.pojos.WmMaterial;
import org.springframework.web.multipart.MultipartFile;
public interface WmMaterialService extends IService<WmMaterial> {
/**
* 图片上传
* @param multipartFile
* @return
*/
public ResponseResult uploadPicture(MultipartFile multipartFile);
}
实现类
package com.heima.wemedia.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.file.service.FileStorageService;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.wemedia.pojos.WmMaterial;
import com.heima.utils.thread.WmThreadLocalUtil;
import com.heima.wemedia.mapper.WmMaterialMapper;
import com.heima.wemedia.service.WmMaterialService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.Date;
import java.util.UUID;
@Slf4j
@Service
@Transactional
public class WmMaterialServiceImpl extends ServiceImpl<WmMaterialMapper, WmMaterial> implements WmMaterialService {
@Autowired
private FileStorageService fileStorageService;
/**
* 图片上传
*
* @param multipartFile
* @return
*/
@Override
public ResponseResult uploadPicture(MultipartFile multipartFile) {
//1.检查参数
if (multipartFile == null || multipartFile.getSize() == 0) {
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
//2.上传图片到minIO
String fileName = UUID.randomUUID().toString().replace("-", "");
//原图片的名字a.jpg
String originalFilename = multipartFile.getOriginalFilename();
String postfix = originalFilename.substring(originalFilename.lastIndexOf("."));
String fileId = null;
try {
fileId = fileStorageService.uploadImgFile("", fileName + postfix, multipartFile.getInputStream());
log.info("上传图片到MinIO中,fileID:{}", fileId);
} catch (IOException e) {
e.printStackTrace();
log.info("WmMaterialServiceImpl-上传文件失败");
}
//3.保存数据到数据库
WmMaterial wmMaterial = new WmMaterial();
wmMaterial.setUserId(WmThreadLocalUtil.getUser().getId());
wmMaterial.setUrl(fileId);
wmMaterial.setIsCollection((short) 0);
wmMaterial.setType((short) 0);
wmMaterial.setCreatedTime(new Date());
save(wmMaterial);
//4.返回结果
return ResponseResult.okResult(wmMaterial);
}
}
3.1.3、素材列表查询
接口定义
①创建WmMaterialDto
package com.heima.model.wemedia.dtos;
import com.heima.model.common.dtos.PageRequestDto;
import lombok.Data;
@Data
public class WmMaterialDto extends PageRequestDto {
/**
* 1 收藏
* 0 未收藏
*/
private Short isCollection;
}
②在WmMaterialController添加方法
@PostMapping("/list")
public ResponseResult findList(@RequestBody WmMaterialDto dto) {
return wmMaterialService.findList(dto);
}
③ 在WmMaterialService中新增方法
/**
* 素材列表查询
* @param dto
* @return
*/
public ResponseResult findList( WmMaterialDto dto);
实现类中实现
/**
* 素材列表查询
*
* @param dto
* @return
*/
@Override
public ResponseResult findList(WmMaterialDto dto) {
//1.检查参数
dto.checkParam();
//2.分页查询
IPage page = new Page(dto.getPage(), dto.getSize());
LambdaQueryWrapper<WmMaterial> lambdaQueryWrapper = new LambdaQueryWrapper<>();
//是否收藏
if (dto.getIsCollection() != null && dto.getIsCollection() == 1) {
lambdaQueryWrapper.eq(WmMaterial::getIsCollection, dto.getIsCollection());
}
//按照用户查询
lambdaQueryWrapper.eq(WmMaterial::getUserId, WmThreadLocalUtil.getUser().getId());
//按照时间倒叙查询
lambdaQueryWrapper.orderByDesc(WmMaterial::getCreatedTime);
page = page(page, lambdaQueryWrapper);
//3.返回结果
ResponseResult responseResult = new PageResponseResult(dto.getPage(), dto.getSize(), (int) page.getTotal());
responseResult.setData(page.getRecords());
return responseResult;
}
④在引导类WemediaApplication中添加mybatis-plus的分页拦截器
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
3.2、自媒体文章管理
3.2.1、查询所有频道
功能如下
功能实现
①创建WmchannelController
package com.heima.wemedia.controller.v1;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.wemedia.service.WmChannelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/channel")
public class WmchannelController {
@Autowired
private WmChannelService wmChannelService;
@GetMapping("/channels")
public ResponseResult findAll(){
return wmChannelService.findAll();
}
}
②创建WmChannelMapper
package com.heima.wemedia.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.heima.model.wemedia.pojos.WmChannel;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface WmChannelMapper extends BaseMapper<WmChannel> {
}
③创建 WmChannelService
package com.heima.wemedia.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.wemedia.pojos.WmChannel;
public interface WmChannelService extends IService<WmChannel> {
/**
* 查询所有频道
* @return
*/
public ResponseResult findAll();
}
实现类
package com.heima.wemedia.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.wemedia.pojos.WmChannel;
import com.heima.wemedia.mapper.WmChannelMapper;
import com.heima.wemedia.service.WmChannelService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
@Slf4j
public class WmChannelServiceImpl extends ServiceImpl<WmChannelMapper, WmChannel> implements WmChannelService {
/**
* 查询所有频道
* @return
*/
@Override
public ResponseResult findAll() {
return ResponseResult.okResult(list());
}
}
3.2.2、查询自媒体文章
接口
功能实现
①创建WmNewsController
package com.heima.wemedia.controller.v1;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.wemedia.dtos.WmNewsPageReqDto;
import com.heima.wemedia.service.WmNewsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/news")
public class WmNewsController {
@Autowired
private WmNewsService wmNewsService;
@PostMapping("/list")
public ResponseResult findAll(@RequestBody WmNewsPageReqDto dto){
return wmNewsService.findAll(dto);
}
}
②创建 WmNewsMapper
package com.heima.wemedia.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.heima.model.wemedia.pojos.WmNews;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface WmNewsMapper extends BaseMapper<WmNews> {
}
③创建WmNewsService
package com.heima.wemedia.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.wemedia.dtos.WmNewsPageReqDto;
import com.heima.model.wemedia.pojos.WmNews;
public interface WmNewsService extends IService<WmNews> {
/**
* 查询文章
* @param dto
* @return
*/
public ResponseResult findAll(WmNewsPageReqDto dto);
}
实现类
package com.heima.wemedia.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.model.common.dtos.PageResponseResult;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.wemedia.dtos.WmNewsPageReqDto;
import com.heima.model.wemedia.pojos.WmNews;
import com.heima.model.wemedia.pojos.WmUser;
import com.heima.utils.thread.WmThreadLocalUtil;
import com.heima.wemedia.mapper.WmNewsMapper;
import com.heima.wemedia.service.WmNewsService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Slf4j
@Transactional
public class WmNewsServiceImpl extends ServiceImpl<WmNewsMapper, WmNews> implements WmNewsService {
/**
* 查询文章
* @param dto
* @return
*/
@Override
public ResponseResult findAll(WmNewsPageReqDto dto) {
//1.检查参数
if(dto == null){
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
//分页参数检查
dto.checkParam();
//获取当前登录人的信息
WmUser user = WmThreadLocalUtil.getUser();
if(user == null){
return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
}
//2.分页条件查询
IPage page = new Page(dto.getPage(),dto.getSize());
LambdaQueryWrapper<WmNews> lambdaQueryWrapper = new LambdaQueryWrapper<>();
//状态精确查询
if(dto.getStatus() != null){
lambdaQueryWrapper.eq(WmNews::getStatus,dto.getStatus());
}
//频道精确查询
if(dto.getChannelId() != null){
lambdaQueryWrapper.eq(WmNews::getChannelId,dto.getChannelId());
}
//时间范围查询
if(dto.getBeginPubDate()!=null && dto.getEndPubDate()!=null){
lambdaQueryWrapper.between(WmNews::getPublishTime,dto.getBeginPubDate(),dto.getEndPubDate());
}
//关键字模糊查询
if(StringUtils.isNotBlank(dto.getKeyword())){
lambdaQueryWrapper.like(WmNews::getTitle,dto.getKeyword());
}
//查询当前登录用户的文章
lambdaQueryWrapper.eq(WmNews::getUserId,user.getId());
//发布时间倒序查询
lambdaQueryWrapper.orderByDesc(WmNews::getCreatedTime);
page = page(page,lambdaQueryWrapper);
//3.结果返回
ResponseResult responseResult = new PageResponseResult(dto.getPage(),dto.getSize(),(int)page.getTotal());
responseResult.setData(page.getRecords());
return responseResult;
}
}
3.2.3、发布文章
需求
表结构
实现思路
接口
WmNewsDto数据结构
响应结果
功能实现
①在新增WmNewsController中新增方法
@PostMapping("/submit")
public ResponseResult submitNews(@RequestBody WmNewsDto dto) {
return wmNewsService.submitNews(dto);
}
②新增WmNewsMaterialMapper类,文章与素材的关联关系需要批量保存,索引需要定义mapper文件和对应的映射文件
package com.heima.wemedia.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.heima.model.wemedia.pojos.WmNewsMaterial;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface WmNewsMaterialMapper extends BaseMapper<WmNewsMaterial> {
void saveRelations(@Param("materialIds") List<Integer> materialIds,@Param("newsId") Integer newsId, @Param("type")Short type);
}
对应的WmNewsMaterialMapper.xml
<?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.heima.wemedia.mapper.WmNewsMaterialMapper">
<insert id="saveRelations">
insert into wm_news_material (material_id,news_id,type,ord)
values
<foreach collection="materialIds" index="ord" item="mid" separator=",">
(#{mid},#{newsId},#{type},#{ord})
</foreach>
</insert>
</mapper>
③在WmNewsService中新增方法
/**
* 发布文章或保存为草稿
* @param dto
* @return
*/
public ResponseResult submitNews(WmNewsDto dto);
实现类中对应的实现方法
/**
* 发布修改文章或保存为草稿
* @param dto
* @return
*/
@Override
public ResponseResult submitNews(WmNewsDto dto) {
//0.条件判断
if(dto == null || dto.getContent() == null){
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
//1.保存或修改文章
WmNews wmNews = new WmNews();
//属性拷贝 属性名词和类型相同才能拷贝
BeanUtils.copyProperties(dto,wmNews);
//封面图片 list---> string
if(dto.getImages() != null && dto.getImages().size() > 0){
//[1dddfsd.jpg,sdlfjldk.jpg]--> 1dddfsd.jpg,sdlfjldk.jpg
String imageStr = StringUtils.join(dto.getImages(), ",");
wmNews.setImages(imageStr);
}
//如果当前封面类型为自动 -1
if(dto.getType().equals(WemediaConstants.WM_NEWS_TYPE_AUTO)){
wmNews.setType(null);
}
saveOrUpdateWmNews(wmNews);
//2.判断是否为草稿 如果为草稿结束当前方法
if(dto.getStatus().equals(WmNews.Status.NORMAL.getCode())){
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
//3.不是草稿,保存文章内容图片与素材的关系
//获取到文章内容中的图片信息
List<String> materials = ectractUrlInfo(dto.getContent());
saveRelativeInfoForContent(materials,wmNews.getId());
//4.不是草稿,保存文章封面图片与素材的关系,如果当前布局是自动,需要匹配封面图片
saveRelativeInfoForCover(dto,wmNews,materials);
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
/**
* 第一个功能:如果当前封面类型为自动,则设置封面类型的数据
* 匹配规则:
* 1,如果内容图片大于等于1,小于3 单图 type 1
* 2,如果内容图片大于等于3 多图 type 3
* 3,如果内容没有图片,无图 type 0
*
* 第二个功能:保存封面图片与素材的关系
* @param dto
* @param wmNews
* @param materials
*/
private void saveRelativeInfoForCover(WmNewsDto dto, WmNews wmNews, List<String> materials) {
List<String> images = dto.getImages();
//如果当前封面类型为自动,则设置封面类型的数据
if(dto.getType().equals(WemediaConstants.WM_NEWS_TYPE_AUTO)){
//多图
if(materials.size() >= 3){
wmNews.setType(WemediaConstants.WM_NEWS_MANY_IMAGE);
images = materials.stream().limit(3).collect(Collectors.toList());
}else if(materials.size() >= 1 && materials.size() < 3){
//单图
wmNews.setType(WemediaConstants.WM_NEWS_SINGLE_IMAGE);
images = materials.stream().limit(1).collect(Collectors.toList());
}else {
//无图
wmNews.setType(WemediaConstants.WM_NEWS_NONE_IMAGE);
}
//修改文章
if(images != null && images.size() > 0){
wmNews.setImages(StringUtils.join(images,","));
}
updateById(wmNews);
}
if(images != null && images.size() > 0){
saveRelativeInfo(images,wmNews.getId(),WemediaConstants.WM_COVER_REFERENCE);
}
}
/**
* 处理文章内容图片与素材的关系
* @param materials
* @param newsId
*/
private void saveRelativeInfoForContent(List<String> materials, Integer newsId) {
saveRelativeInfo(materials,newsId,WemediaConstants.WM_CONTENT_REFERENCE);
}
@Autowired
private WmMaterialMapper wmMaterialMapper;
/**
* 保存文章图片与素材的关系到数据库中
* @param materials
* @param newsId
* @param type
*/
private void saveRelativeInfo(List<String> materials, Integer newsId, Short type) {
if(materials!=null && !materials.isEmpty()){
//通过图片的url查询素材的id
List<WmMaterial> dbMaterials = wmMaterialMapper.selectList(Wrappers.<WmMaterial>lambdaQuery().in(WmMaterial::getUrl, materials));
//判断素材是否有效
if(dbMaterials==null || dbMaterials.size() == 0){
//手动抛出异常 第一个功能:能够提示调用者素材失效了,第二个功能,进行数据的回滚
throw new CustomException(AppHttpCodeEnum.MATERIASL_REFERENCE_FAIL);
}
if(materials.size() != dbMaterials.size()){
throw new CustomException(AppHttpCodeEnum.MATERIASL_REFERENCE_FAIL);
}
List<Integer> idList = dbMaterials.stream().map(WmMaterial::getId).collect(Collectors.toList());
//批量保存
wmNewsMaterialMapper.saveRelations(idList,newsId,type);
}
}
/**
* 提取文章内容中的图片信息
* @param content
* @return
*/
private List<String> ectractUrlInfo(String content) {
List<String> materials = new ArrayList<>();
List<Map> maps = JSON.parseArray(content, Map.class);
for (Map map : maps) {
if(map.get("type").equals("image")){
String imgUrl = (String) map.get("value");
materials.add(imgUrl);
}
}
return materials;
}
@Autowired
private WmNewsMaterialMapper wmNewsMaterialMapper;
/**
* 保存或修改文章
* @param wmNews
*/
private void saveOrUpdateWmNews(WmNews wmNews) {
//补全属性
wmNews.setUserId(WmThreadLocalUtil.getUser().getId());
wmNews.setCreatedTime(new Date());
wmNews.setSubmitedTime(new Date());
wmNews.setEnable((short)1);//默认上架
if(wmNews.getId() == null){
//保存
save(wmNews);
}else {
//修改
//删除文章图片与素材的关系
wmNewsMaterialMapper.delete(Wrappers.<WmNewsMaterial>lambdaQuery().eq(WmNewsMaterial::getNewsId,wmNews.getId()));
updateById(wmNews);
}
}