新闻头条项目

新闻头条项目

一、项目介绍

在这里插入图片描述

二、功能架构

在这里插入图片描述

1.平台管理端功能大纲

在这里插入图片描述

2.自媒体端功能大纲

在这里插入图片描述

3.APP主要功能大纲

在这里插入图片描述

三、数据库

在这里插入图片描述
在这里插入图片描述

四、通用接口说明

1.通用响应对象PageResponseResult

在这里插入图片描述

2.通用的请求dtos

在这里插入图片描述

3.通用的异常枚举

在这里插入图片描述

五、平台管理端功能

1.频道管理

在这里插入图片描述
在这里插入图片描述

curd的说明
先开发对应实体类
package com.heima.model.admin.pojos;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;

/**
 * <p>
 * 频道信息表
 * </p>
 *
 * @author itheima
 */
@Data
@TableName("ad_channel")
public class AdChannel implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 频道名称
     */
    @TableField("name")
    private String name;

    /**
     * 频道描述
     */
    @TableField("description")
    private String description;

    /**
     * 是否默认频道
     */
    @TableField("is_default")
    private Boolean isDefault;

    @TableField("status")
    private Boolean status;

    /**
     * 默认排序
     */
    @TableField("ord")
    private Integer ord;

    /**
     * 创建时间
     */
    @TableField("created_time")
    private Date createdTime;

}
分页

采用mybatis-plus分页插件PaginationInterceptor,写在springboot引导类中

定义微服务接口

在这里插入图片描述
在这里插入图片描述

持久层

在这里插入图片描述

业务层

在这里插入图片描述
业务层实现类

package com.heima.admin.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.admin.mapper.AdChannelMapper;
import com.heima.admin.service.AdChannelService;
import com.heima.model.admin.dtos.ChannelDto;
import com.heima.model.admin.pojos.AdChannel;
import com.heima.model.common.dtos.PageResponseResult;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

@Service
public class AdChannelServiceImpl extends ServiceImpl<AdChannelMapper, AdChannel> implements AdChannelService {



    @Override
    public ResponseResult findByNameAndPage(ChannelDto dto) {

        //1.参数检测
        if(dto==null){
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        //分页参数检查
        dto.checkParam();

        //2.安装名称模糊分页查询
        Page page = new Page(dto.getPage(),dto.getSize());
        LambdaQueryWrapper<AdChannel> lambdaQueryWrapper = new LambdaQueryWrapper();
        if(StringUtils.isNotBlank(dto.getName())){
            lambdaQueryWrapper.like(AdChannel::getName,dto.getName());
        }
        IPage result = page(page, lambdaQueryWrapper);

        //3.结果封装
        ResponseResult responseResult = new PageResponseResult(dto.getPage(),dto.getSize(),(int)result.getTotal());
        responseResult.setData(result.getRecords());
        return responseResult;
    }
}
控制层

在这里插入图片描述

2.敏感词管理

在这里插入图片描述
在这里插入图片描述

3.登录功能

加密前置知识
MD5密码加密

md5相同的密码每次加密都一样,不太安全

手动加密(md5+随机字符串)

在这里插入图片描述

BCrypt密码加密
String gensalt = BCrypt.gensalt();//这个是盐  29个字符,随机生成
System.out.println(gensalt);
String password = BCrypt.hashpw("123456", gensalt);  //根据盐对密码进行加密
System.out.println(password);//加密后的字符串前29位就是盐
jwt介绍
token认证

在这里插入图片描述

什么是JWT?

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

登录功能加入网关gateway

全局过滤器实现jwt校验
在这里插入图片描述
在这里插入图片描述

4.用户认证(审核是核心功能)

在这里插入图片描述
在这里插入图片描述

核心功能:用户认证后审核

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
@EnableFeignClients使用feign进行远程调用,平台管理admin端远程调用自媒体用户和文章作者模块。

分布式事务解决认证过程中数据不一致问题
CAP定理

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

BASE理论

在这里插入图片描述

六、自媒体端功能

1.自媒体用户保存

在这里插入图片描述
在新建自媒体账户时需要把apuser信息赋值给自媒体用户

2.查询作者和保存作者

在这里插入图片描述

3.素材管理

上传图片到fastdfs,同时要保存一份数据到表中,方便后期管理
在这里插入图片描述

图片上传

在这里插入图片描述
在这里插入图片描述

图片删除

在这里插入图片描述

4.自媒体用户登录

自媒体登录操作与admin端登录思路是一样的

5.自媒体端文章列表

在这里插入图片描述
在这里插入图片描述

6.自媒体文章发布

在这里插入图片描述
在这里插入图片描述

文章删除

在这里插入图片描述
当文章状态为9(已发布)且已上架则不能删除文章,下架状态可以删除,如果是其他状态可以删除
删除文章之前需要先把素材与文章的关系删除掉

文章上下架

当前已经发布(状态为9)的文章可以上架(enable = 1),也可以下架(enable = 0)
在上架和下架操作的同时,需要同步app端的文章配置信息,暂时不做,后期讲到审核文章的时候再优化

7. 自媒体文章审核(核心功能)

在这里插入图片描述
在这里插入图片描述

表结构

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

分布式id

后期由于文章数量较多,需要采用分库分表,id为自增策略则可能产生重复id。
在这里插入图片描述
在这里插入图片描述

定时任务扫描待发布文章

在这里插入图片描述

七、全局异常处理

@ControllerAdvice和@ExceptionHandler注解配合使用
在这里插入图片描述

八、项目的问题

1.项目中用到了nacos,它与eureka有什么区别

在这里插入图片描述

2.分布式事务解决方案

基于XA协议的两阶段提交

在这里插入图片描述
在这里插入图片描述

TCC补偿机制

在这里插入图片描述
在这里插入图片描述

消息最终一致性

在这里插入图片描述
在这里插入图片描述

3.项目中使用Seata实现分布式事务

Seata事务模式-AT模式

在这里插入图片描述

4.分布式文件系统FastDFS

在这里插入图片描述
在这里插入图片描述
上传流程
在这里插入图片描述

5.kafka面试题

相关概念

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

发送消息的工作原理

在这里插入图片描述

消费者工作原理

在这里插入图片描述

九、项目核心功能

1.登录

前置知识
常见的加密方式
可逆加密算法

解释: 加密后, 密文可以反向解密得到密码原文.

对称加密
在这里插入图片描述
非对称加密
在这里插入图片描述
在这里插入图片描述

不可逆加密算法

在这里插入图片描述

Base64编码

在这里插入图片描述

密码加密的方式选型
MD5密码加密

在这里插入图片描述

手动加密(md5+随机字符串)

在这里插入图片描述

BCrypt密码加密

在这里插入图片描述

boolean checkpw = BCrypt.checkpw("123456",     "$2a$10$61ogZY7EXsMDWeVGQpDq3OBF1.phaUu7.xrwLyWFTOu8woE08zMIW");
System.out.println(checkpw);
项目中最终采用的方式

项目中采用第二种手动加盐的方式进行加密
请添加图片描述

jwt介绍
token认证

在这里插入图片描述

什么是JWT?

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

生成token

需要引入jwt相关依赖

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
</dependency>

工具类

package com.heima.utils.common;

import io.jsonwebtoken.*;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.*;

public class AppJwtUtil {

    // TOKEN的有效期一天(S)
    private static final int TOKEN_TIME_OUT = 3_600;
    // 加密KEY
    private static final String TOKEN_ENCRY_KEY = "MDk4ZjZiY2Q0NjIxZDM3M2NhZGU0ZTgzMjYyN2I0ZjY";
    // 最小刷新间隔(S)
    private static final int REFRESH_TIME = 300;

    // 生产ID
    public static String getToken(Long id){
        Map<String, Object> claimMaps = new HashMap<>();
        claimMaps.put("id",id);
        long currentTime = System.currentTimeMillis();
        return Jwts.builder()
                .setId(UUID.randomUUID().toString())
                .setIssuedAt(new Date(currentTime))  //签发时间
                .setSubject("system")  //说明
                .setIssuer("heima") //签发者信息
                .setAudience("app")  //接收用户
                .compressWith(CompressionCodecs.GZIP)  //数据压缩方式
                .signWith(SignatureAlgorithm.HS512, generalKey()) //加密方式
                .setExpiration(new Date(currentTime + TOKEN_TIME_OUT * 1000))  //过期时间戳
                .addClaims(claimMaps) //cla信息
                .compact();
    }

    /**
     * 获取token中的claims信息
     *
     * @param token
     * @return
     */
    private static Jws<Claims> getJws(String token) {
            return Jwts.parser()
                    .setSigningKey(generalKey())
                    .parseClaimsJws(token);
    }

    /**
     * 获取payload body信息
     *
     * @param token
     * @return
     */
    public static Claims getClaimsBody(String token) {
        try {
            return getJws(token).getBody();
        }catch (ExpiredJwtException e){
            return null;
        }
    }

    /**
     * 获取hearder body信息
     *
     * @param token
     * @return
     */
    public static JwsHeader getHeaderBody(String token) {
        return getJws(token).getHeader();
    }

    /**
     * 是否过期
     *
     * @param claims
     * @return -1:有效,0:有效,1:过期,2:过期
     */
    public static int verifyToken(Claims claims) {
        if(claims==null){
            return 1;
        }
        try {
            claims.getExpiration()
                    .before(new Date());
            // 需要自动刷新TOKEN
            if((claims.getExpiration().getTime()-System.currentTimeMillis())>REFRESH_TIME*1000){
                return -1;
            }else {
                return 0;
            }
        } catch (ExpiredJwtException ex) {
            return 1;
        }catch (Exception e){
            return 2;
        }
    }

    /**
     * 由字符串生成加密key
     *
     * @return
     */
    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.getEncoder().encode(TOKEN_ENCRY_KEY.getBytes());
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }

    public static void main(String[] args) {
       /* Map map = new HashMap();
        map.put("id","11");*/
        System.out.println(AppJwtUtil.getToken(1102L));
        Jws<Claims> jws = AppJwtUtil.getJws("eyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAADWLQQqEMAwA_5KzhURNt_qb1KZYQSi0wi6Lf9942NsMw3zh6AVW2DYmDGl2WabkZgreCaM6VXzhFBfJMcMARTqsxIG9Z888QLui3e3Tup5Pb81013KKmVzJTGo11nf9n8v4nMUaEY73DzTabjmDAAAA.4SuqQ42IGqCgBai6qd4RaVpVxTlZIWC826QA9kLvt9d-yVUw82gU47HDaSfOzgAcloZedYNNpUcd18Ne8vvjQA");
        Claims claims = jws.getBody();
        System.out.println(claims.get("id"));

    }
}
token相对于session和cookie的优势

优势

项目中登录功能的实现方式
流程图

在这里插入图片描述
在这里插入图片描述

网关中全局过滤器的实现
package com.heima.admin.gateway.filter;

import com.heima.admin.gateway.utils.AppJwtUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
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
@Log4j2
public class AuthorizeFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.获取请求对象和响应对象
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        //2.判断当前的请求是否为登录,如果是,直接放行
        if(request.getURI().getPath().contains("/login/in")){
            //放行
            return chain.filter(exchange);
        }

        //3.获取当前用户的请求头jwt信息
        HttpHeaders headers = request.getHeaders();
        String jwtToken = headers.getFirst("token");

        //4.判断当前令牌是否存在
        if(StringUtils.isEmpty(jwtToken)){
            //如果不存在,向客户端返回错误提示信息
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }

        try {
            //5.如果令牌存在,解析jwt令牌,判断该令牌是否合法,如果不合法,则向客户端返回错误信息
            Claims claims = AppJwtUtil.getClaimsBody(jwtToken);
            int result = AppJwtUtil.verifyToken(claims);
            if(result == 0 || result == -1){
                //5.1 合法,则向header中重新设置userId
                Integer id = (Integer) claims.get("id");
                log.info("find userid:{} from uri:{}",id,request.getURI());
                //重新设置token到header中
                ServerHttpRequest serverHttpRequest = request.mutate().headers(httpHeaders -> {
                    httpHeaders.add("userId", id + "");
                }).build();
                exchange.mutate().request(serverHttpRequest).build();
            }
        }catch (Exception e){
            e.printStackTrace();
            //想客户端返回错误提示信息
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }


        //6.放行
        return chain.filter(exchange);
    }

    /**
     * 优先级设置
     * 值越小,优先级越高
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值