尚硅谷SpringBoot项目--微头条实战(有完整项目代码)

目录

项目模块

技术栈

功能展示

环境搭建

前端环境搭建:

后端环境搭建:

数据库创建:

(建议使用数据库软件操作)

springboot项目搭建:

mybatis-plus逆向工程

后端功能开发

用户模块开发

jwt 与 token

登录功能实现

根据token获取用户信息

注册用户名检查

用户注册功能

首页模块开发

查询首页分类

分页查询首页头条信息

查询头条详情

头条模块开发

登录验证和保护

头条发布实现

修改头条回显

头条修改实现

删除头条功能


完整后端代码:https://pan.baidu.com/s/1sEsK4x6uM_o32qdsVTxagQ?pwd=d67f

apifox测试文件:https://pan.baidu.com/s/11liz1MxjYeitrsJbua3VFA?pwd=k8we

项目模块

  • 用户功能
    • 注册功能
    • 登录功能
    • jwt实现
  • 头条新闻
    • 新闻的分页浏览
    • 通过标题关键字搜索新闻
    • 查看新闻详情
    • 新闻的修改和删除

技术栈

前端技术栈:

  • ES6作为基础JS语法
  • nodejs用于运行环境
  • npm用于项目依赖管理工具
  • vite用于项目的构建架工具
  • Vue3用于项目数据的渲染框架
  • Axios用于前后端数据的交互
  • Router用于页面的跳转
  • Pinia用于存储用户的数据
  • LocalStorage作为用户校验token的存储手段
  • Element-Plus提供组件

后端技术栈:

  • JAVA作为开发语言,版本为JDK17
  • Tomcat作为服务容器,版本为10.1.7
  • Mysql8用于项目存储数据
  • SpringMVC用于控制层实现前后端数据交互
  • MyBatis-Plus用于实现数据的CURD
  • Druid用于提供数据源的连接池
  • SpringBoot作为项目基础架构
  • MD5用于用户密码的加密
  • Jwt用于token的生成和校验
  • Jackson用于转换JSON

功能展示

头条首页信息搜索

登录功能

注册功能

展示功能

发布头条功能

修改头条功能

环境搭建

前端环境搭建:

前端环境下载icon-default.png?t=N7T8https://pan.baidu.com/s/16HJROtpdTZ6DrL0GMNJncw?pwd=42wf

下载后解压,使用vscode打开文件夹,在vscode中打开终端依次输入:

npm install

npm run dev

后项目启动:

后端环境搭建:

数据库创建:
(建议使用数据库软件操作)

https://pan.baidu.com/s/19vKtJCn_CwyFHPwinpcmsA?pwd=fkky

springboot项目搭建:

1.创建boot工程:springboot-news

2.导入依赖

<?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">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.5</version>
    </parent>

    <groupId>com.qiu</groupId>
    <artifactId>springboot-news</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- mybatis-plus  -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.3.1</version>
        </dependency>

        <!-- 数据库相关配置启动器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <!-- druid启动器的依赖  -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-3-starter</artifactId>
            <version>1.2.18</version>
        </dependency>

        <!-- 驱动类-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.28</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

3.编写配置

# server配置
server:
  port: 8080
  servlet:
    context-path: /

# 连接池配置
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      url: jdbc:mysql:///sm_db
      username: root
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.Driver

# mybatis-plus的配置
mybatis-plus:
  type-aliases-package: com.qiu.pojo
  global-config:
    db-config:
      logic-delete-field: isDeleted  #全局逻辑删除
      id-type: auto #主键策略自增长
      table-prefix: news_ # 设置表的前缀

4.解决druid问题

druid版本在1.2.20以下的需要:创建文件,添加内容

文件名:org.springframework.boot.autoconfigure.AutoConfiguration.imports
内容:com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure

5.编写启动类和mybatis-plus插件

包:com.qiu

@SpringBootApplication
@MapperScan("com.qiu.mapper")
public class Main {
    public static void main(String[] args) {
        System.out.println("Hello world!");
        SpringApplication.run(Main.class,args);
    }
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        //分页
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        //乐观锁
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        //防止全局修改和删除
        interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
        return interceptor;
    }
}

6.编写工具类

包:com.qiu.utils

结果封装类

/**
 * 全局统一返回结果类
 */
public class Result<T> {
    // 返回码
    private Integer code;
    // 返回消息
    private String message;
    // 返回数据
    private T data;
    public Result(){}
    // 返回数据
    protected static <T> Result<T> build(T data) {
        Result<T> result = new Result<T>();
        if (data != null)
            result.setData(data);
        return result;
    }
    public static <T> Result<T> build(T body, Integer code, String message) {
        Result<T> result = build(body);
        result.setCode(code);
        result.setMessage(message);
        return result;
    }
    public static <T> Result<T> build(T body, ResultCodeEnum resultCodeEnum) {
        Result<T> result = build(body);
        result.setCode(resultCodeEnum.getCode());
        result.setMessage(resultCodeEnum.getMessage());
        return result;
    }
    /**
     * 操作成功
     * @param data  baseCategory1List
     * @param <T>
     * @return
     */
    public static<T> Result<T> ok(T data){
        Result<T> result = build(data);
        return build(data, ResultCodeEnum.SUCCESS);
    }
    public Result<T> message(String msg){
        this.setMessage(msg);
        return this;
    }
    public Result<T> code(Integer code){
        this.setCode(code);
        return this;
    }
    public Integer getCode() {
        return code;
    }
    public void setCode(Integer code) {
        this.code = code;
    }
    public String getMessage() {
        return message;
    }
    public void setMessage(String message) {
        this.message = message;
    }
    public T getData() {
        return data;
    }
    public void setData(T data) {
        this.data = data;
    }
}

结果状态信息枚举类

/**
 * 统一返回结果状态信息类
 *
 */
public enum ResultCodeEnum {

    SUCCESS(200,"success"),
    USERNAME_ERROR(501,"usernameError"),
    PASSWORD_ERROR(503,"passwordError"),
    NOTLOGIN(504,"notLogin"),
    USERNAME_USED(505,"userNameUsed");

    private Integer code;
    private String message;
    private ResultCodeEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }
    public Integer getCode() {
        return code;
    }
    public String getMessage() {
        return message;
    }
}

MD5加密工具类

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

@Component
public final class MD5Util {
    public static String encrypt(String strSrc) {
        try {
            char hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
                    '9', 'a', 'b', 'c', 'd', 'e', 'f' };
            byte[] bytes = strSrc.getBytes();
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(bytes);
            bytes = md.digest();
            int j = bytes.length;
            char[] chars = new char[j * 2];
            int k = 0;
            for (int i = 0; i < bytes.length; i++) {
                byte b = bytes[i];
                chars[k++] = hexChars[b >>> 4 & 0xf];
                chars[k++] = hexChars[b & 0xf];
            }
            return new String(chars);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            throw new RuntimeException("MD5加密出错!!+" + e);
        }
    }
}
mybatis-plus逆向工程

1.逆向工程

2.完善pojo类

删除@TableName注解,全局统一设置

添加主键,乐观锁,逻辑删除注解

@Data
public class User implements Serializable {
    @TableId
    private Integer uid;

    private String username;

    private String userPwd;

    private String nickName;
    @Version
    private Integer version;
    @TableLogic
    private Integer isDeleted;

    private static final long serialVersionUID = 1L;
}

后端功能开发

用户模块开发

jwt 与 token

令牌(token):用于验证用户身份或授权用户对特定资源的访问。普通令牌可以以多种形式出现:访问令牌,身份令牌,刷新令牌等。

就是在用户登录后生成一段字符或数字给他,用户之后每次访问都携带这个token来证明自己的身份,这段字符或数字就是token

JWT介绍

Token是一项规范和标准(接口)

JWT(JSON Web Token)是具体可以生成,校验,解析等动作Token的技术(实现类)

JWT工作流程

  1. 用户通过其凭据(通常为账号密码)进行身份认证
  2. 服务器对凭据进行验证,验证成功后创建一个JWT
  3. 服务器将JWT发送给客户端,客户端在之后的请求中将JWT添加到请求头或参数中
  4. 服务器接收请求后,验证JWT的签名和有效性,并根据JWT中的声明进行身份验证和授权操作

JWT的数据组成和包含信息

JWT由三部分组成: header(头部).payload(载荷).signature(签名)

jwt可以携带很多信息:有效时间,签名秘钥,其他用户标识信息等

有效时间为了保证token的时效性,过期可以重新登录获取

签名秘钥为了防止其他人随意解析和校验token数据

用户信息为了我们自己解析的时候,知道Token对应的具体用户

JWT的使用

1.导入依赖

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

<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.0</version>
</dependency>

2.编写配置

application.yaml

jwt:
  token:
#    tokenExpiration: 120 #有效时间,单位分钟
    tokenExpiration: 1 #为了测试token过期,设置一分钟
    tokenSignKey: qiu666 #当前程序签名秘钥 自定义

3.导入工具类

@Component
@Data
@ConfigurationProperties(prefix = "jwt.token")
public class JwtHelper {
    //有效时间,单位毫秒 1000毫秒 == 1秒
    private long tokenExpiration;
    //当前程序签名秘钥
    private String tokenSignKey;
    //生成token
    public String createToken(Long userId){
        String token = Jwts.builder().setSubject("YYGH-USER")
//                1分钟
                .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration * 1000 * 60))
                //存放数据
                .claim("userId", userId)
                //签名,当用户请求时携带token,根据签名判断身份是否通过
                .signWith(SignatureAlgorithm.HS512, tokenSignKey)
                .compressWith(CompressionCodecs.GZIP).compact();
        return token;
    }
    //当用户请求中携带token时,从token中获取数据(useId)
    public Long getUserId(String token){
        //StringUtils的包为com.baomidou.mybatisplus.core.toolkit.StringUtils;
        if (StringUtils.isEmpty(token))return null;
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
        Claims claims = claimsJws.getBody();
        Integer userId = (Integer) claims.get("userId");
        return userId.longValue();
    }
    //判断token是否有效
    public boolean isExpiration(String token){
        try {
            boolean isExpire = Jwts.parser()
                    .setSigningKey(tokenSignKey)
                    .parseClaimsJws(token)
                    .getBody()
                    .getExpiration().before(new Date());
            //没有过期,有效,返回false
            return isExpire;
        }catch (Exception e){
            //过期,返回true
            return true;
        }
    }

}

4.使用和测试

@SpringBootTest
public class JwtTest {
    @Autowired
    private JwtHelper jwtHelper;
    @Test
    public void test() throws InterruptedException {
        //生成数据 用户数据 userId 1L
        String token = jwtHelper.createToken(1L);
        System.out.println("token = " + token);
        //解析用户标识
        Long userId = jwtHelper.getUserId(token);
        System.out.println("userId = " + userId);
        //查看token是否过期 false 未到期 true到期
        boolean expiration = jwtHelper.isExpiration(token);;
        System.out.println("expiration = " + expiration);
        Thread.sleep(1000*60);
        System.out.println(" 程序睡眠一分钟后: ");
        //查看token是否过期 false 未到期 true到期
        boolean expiration1 = jwtHelper.isExpiration(token);;
        System.out.println("expiration = " + expiration1);
    }
}

登录功能实现

1.需求:用户在客户端输入用户名密码并向后端提交,后端根据用户名和密码判断登录是否成功,用户有误或者密码有误响应不同的提示信息!

2.接口描述:

url:user/login

请求方式:POST

请求参数:

{
    "username":"zhangsan",
    "userPwd":"123456"
}

响应数据:

成功

{
   "code":"200",         // 成功状态码 
   "message":"success"   // 成功状态描述
   "data":{
    "token":"... ..." // 用户id的token
  }
}

失败

{
   "code":"501",
   "message":"用户名有误"
   "data":{}
}
或者
{
   "code":"503",
   "message":"密码有误"
   "data":{}
}

3.实现代码:

controller:

@RestController
@RequestMapping("user")
@CrossOrigin    //解决跨域问题
public class UserController {
    @Autowired
    private UserService userService;

    @PostMapping("login")
    public Result<User> login(@RequestBody User user){
        Result<User> result = userService.login(user);
        return result;
    }

}

service:

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
    implements UserService{
    @Autowired
    private JwtHelper jwtHelper;
    @Autowired
    private  UserMapper userMapper;

    /**
     * 登录业务实现
     * @param user
     * @return result封装
     */
    @Override
    public Result login(User user) {

        //根据账号查询
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getUsername,user.getUsername());
        User loginUser = userMapper.selectOne(queryWrapper);

        //账号判断
        if (loginUser == null) {
            //账号错误
            return Result.build(null, ResultCodeEnum.USERNAME_ERROR);
        }

        //判断密码
        if (!StringUtils.isEmpty(user.getUserPwd())
                && loginUser.getUserPwd().equals(MD5Util.encrypt(user.getUserPwd())))
        {
           //账号密码正确
            //根据用户唯一标识生成token
            String token = jwtHelper.createToken(Long.valueOf(loginUser.getUid()));

            Map data = new HashMap();
            data.put("token",token);

            return Result.ok(data);
        }

        //密码错误
        return Result.build(null,ResultCodeEnum.PASSWORD_ERROR);
    }
}

测试结果

根据token获取用户信息

1.需求:客户端发送请求,提交token请求头,后端根据token请求头获取登录用户的详细信息并响应给客户端进行存储

2.接口描述:

url:user/getUserInfo

请求方式:GET

请求头:

token:token内容

响应数据:

成功:

{
    "code": 200,
    "message": "success",
    "data": {
        "loginUser": {
            "uid": 1,
            "username": "zhangsan",
            "userPwd": "",
            "nickName": "张三"
        }
    }
}

失败:

{
    "code": 504,
    "message": "notLogin",
    "data": null
}

代码实现:

controller:

/**
 * 地址: user/getUserInfo
 * 方式: get
 * 请求头: token = token内容
 * 返回:
 *    {
 *     "code": 200,
 *     "message": "success",
 *     "data": {
 *         "loginUser": {
 *             "uid": 1,
 *             "username": "zhangsan",
 *             "userPwd": "",
 *             "nickName": "张三"
 *         }
 *      }
 *   }
 *
 * 大概流程:
 *    1.获取token,解析token对应的userId
 *    2.根据userId,查询用户数据
 *    3.将用户数据的密码置空,并且把用户数据封装到结果中key = loginUser
 *    4.失败返回504 (本次先写到当前业务,后期提取到拦截器和全局异常处理器)
 */
@GetMapping("getUserInfo")
public Result userInfo(@RequestHeader String token){
    Result result = userService.getUserInfo(token);
    return result;
}

service:

/**
 * 查询用户数据
 * @param token
 * @return result封装
 */
@Override
public Result getUserInfo(String token) {

    //1.判定是否有效期
    if (jwtHelper.isExpiration(token)) {
        //true过期,直接返回未登录
        return Result.build(null,ResultCodeEnum.NOTLOGIN);
    }

    //2.获取token对应的用户
    int userId = jwtHelper.getUserId(token).intValue();

    //3.查询数据
    User user = userMapper.selectById(userId);

    if (user != null) {
        user.setUserPwd(null);
        Map data = new HashMap();
        data.put("loginUser",user);
        return Result.ok(data);
    }

    return Result.build(null,ResultCodeEnum.NOTLOGIN);
}

测试结果:

注册用户名检查

1.需求:用户在注册时输入用户名时,立刻将用户名发送给后端,后端根据用户名查询用户名是否可用并做出响应

2.接口描述:

url:user/checkUserName

请求方式:POST

请求参数:param形式

username=zhangsan

响应数据:

成功:

{
   "code":"200",
   "message":"success"
   "data":{}
}

失败:

{
    "code":"505",
   "message":"用户名占用"
   "data":{}
}

3.代码实现:

controller:

/**
 * url地址:user/checkUserName
 * 请求方式:POST
 * 请求参数:param形式
 * username=zhangsan
 * 响应数据:
 * {
 *    "code":"200",
 *    "message":"success"
 *    "data":{}
 * }
 *
 * 实现步骤:
 *   1. 获取账号数据
 *   2. 根据账号进行数据库查询
 *   3. 结果封装
 */
@PostMapping("checkUserName")
public Result checkUserName(String username){
    Result result = userService.checkUserName(username);
    return result;
}

service:

/**
 * 检查账号是否可以注册
 *
 * @param username 账号信息
 * @return
 */
@Override
public Result checkUserName(String username) {

    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(User::getUsername,username);
    User user = userMapper.selectOne(queryWrapper);

    if (user != null){
        return Result.build(null,ResultCodeEnum.USERNAME_USED);
    }

    return Result.ok(null);
}

测试结果:

用户注册功能

1.需求:客户端将新用户信息发送给服务端,服务端将新用户存入数据库,存入之前做用户名是否被占用校验,校验通过响应成功提示,否则响应失败提示

2.接口描述:

url:user/regist

请求方式:POST

请求参数:

{
    "username":"zhangsan",
    "userPwd":"123456", 
    "nickName":"张三"
}

响应数据:

成功:

{
   "code":"200",
   "message":"success"
   "data":{}
}

失败:
 

{
   "code":"505",
   "message":"用户名占用"
   "data":{}
}

3.代码实现:

controller:

/**
* url地址:user/regist
* 请求方式:POST
* 请求参数:
* {
*     "username":"zhangsan",
*     "userPwd":"123456",
*     "nickName":"张三"
* }
* 响应数据:
* {
*    "code":"200",
*    "message":"success"
*    "data":{}
* }
*
* 实现步骤:
*   1. 将密码加密
*   2. 将数据插入
*   3. 判断结果,成 返回200 失败 505
*/

@PostMapping("regist")
public Result regist(@RequestBody User user){
  Result result = userService.regist(user);
  return result;
}

service:

@Override
public Result regist(User user) {
    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(User::getUsername,user.getUsername());
    Long count = userMapper.selectCount(queryWrapper);

    if (count > 0){
        return Result.build(null,ResultCodeEnum.USERNAME_USED);
    }

    user.setUserPwd(MD5Util.encrypt(user.getUserPwd()));
    int rows = userMapper.insert(user);
    System.out.println("rows = " + rows);
    return Result.ok(null);
}

测试结果:

首页模块开发

查询首页分类

1.需求:

进入新闻首页,查询所有分类并动态展示新闻类别栏位

2.接口描述:

url:portal/findAllTypes

请求方式:GET

请求参数:无

响应数据:

成功:

{
   "code":"200",
   "message":"OK"
   "data":{
            [
                {
                    "tid":"1",
                    "tname":"新闻"
                },
                {
                    "tid":"2",
                    "tname":"体育"
                },
                {
                    "tid":"3",
                    "tname":"娱乐"
                },
                {
                    "tid":"4",
                    "tname":"科技"
                },
                {
                    "tid":"5",
                    "tname":"其他"
                }
            ]
    }
}

3.代码实现

controller:

@RestController
@RequestMapping("portal")
@CrossOrigin
public class PortalController {

    @Autowired
    private TypeService typeService;

    /**
     * 查询全部类别信息
     * @return
     */
    @GetMapping("findAllTypes")
    public Result findAllTypes(){
        //直接调用业务层,查询全部数据
        List<Type> list = typeService.list();
        return  Result.ok(list);
    }
}

测试结果:

分页查询首页头条信息

1.需求:

        客户端向服务端发送查询关键字,新闻类别,页码数,页大小

        服务端根据条件搜索分页信息,返回含页码数,页大小,总页数,总记录数,当前页数据等信息,并根据时间降序,浏览量降序排序

2.接口描述:

url:portal/findNewsPage

请求方式:POST

请求参数:

{
    "keyWords":"马斯克", // 搜索标题关键字
    "type":0,           // 新闻类型
    "pageNum":1,        // 页码数
    "pageSize":10     // 页大小
}

响应数据:

成功:

{
   "code":"200",
   "message":"success"
   "data":{
      "pageInfo":{
        "pageData":[
          {
            "hid":"1",                     // 新闻id 
            "title":"尚硅谷宣布 ... ...",   // 新闻标题
            "type":"1",                    // 新闻所属类别编号
            "pageViews":"40",              // 新闻浏览量
            "pastHours":"3" ,              // 发布时间已过小时数
            "publisher":"1"                // 发布用户ID
        },
        {
            "hid":"1",                     // 新闻id 
            "title":"尚硅谷宣布 ... ...",   // 新闻标题
            "type":"1",                    // 新闻所属类别编号
            "pageViews":"40",              // 新闻浏览量
            "pastHours":"3",              // 发布时间已过小时数
            "publisher":"1"                // 发布用户ID
        },
        {
            "hid":"1",                     // 新闻id 
            "title":"尚硅谷宣布 ... ...",   // 新闻标题
            "type":"1",                    // 新闻所属类别编号
            "pageViews":"40",              // 新闻浏览量
            "pastHours":"3",               // 发布时间已过小时数
            "publisher":"1"                // 发布用户ID
        }
        ],
      "pageNum":1,    //页码数
      "pageSize":10,  // 页大小
      "totalPage":20, // 总页数
      "totalSize":200 // 总记录数
    }
  }
}

3.代码实现:

准备接收条件类:

@Data
public class PortalVo {
    
    private String keyWords;
    private Integer type;
    private Integer pageNum = 1;
    private Integer pageSize =10;
}

controller:

/**
 * 首页分页查询
 * @return
 */
@PostMapping("findNewPages")
public Result findNewPage(@RequestBody PortalVo portalVo){
    Result result = headlineService.findNewPage(portalVo);
    return result;
}

service:

@Service
public class HeadlineServiceImpl extends ServiceImpl<HeadlineMapper, Headline>
    implements HeadlineService{

    @Autowired
    private HeadlineMapper headlineMapper;

    /**
     * 首页数据查询
     * @param portalVo
     * @return
     */
    @Override
    public Result findNewPage(PortalVo portalVo) {

        //1.条件拼接 需要非空判断
        LambdaQueryWrapper<Headline> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.like(!StringUtils.isEmpty(portalVo.getKeyWords()),Headline::getTitle,portalVo.getKeyWords())
                .eq(portalVo.getType()!= null,Headline::getType,portalVo.getType());

        //2.分页参数
        IPage<Headline> page = new Page<>(portalVo.getPageNum(),portalVo.getPageSize());

        //3.分页查询
        //查询的结果 "pastHours":"3"   // 发布时间已过小时数 我们查询返回一个map
        //自定义方法
        headlineMapper.selectPageMap(page, portalVo);

        //4.结果封装
        //分页数据封装
        Map<String,Object> pageInfo =new HashMap<>();
        pageInfo.put("pageData",page.getRecords());
        pageInfo.put("pageNum",page.getCurrent());
        pageInfo.put("pageSize",page.getSize());
        pageInfo.put("totalPage",page.getPages());
        pageInfo.put("totalSize",page.getTotal());

        Map<String,Object> pageInfoMap=new HashMap<>();
        pageInfoMap.put("pageInfo",pageInfo);
        // 响应JSON
        return Result.ok(pageInfoMap);
    }
}

mapper:

接口:

public interface HeadlineMapper extends BaseMapper<Headline> {

    //自定义分页查询方法
    IPage<Map> selectPageMap(IPage<Headline> page, 
                @Param("portalVo") PortalVo portalVo);
}

mapper.xml:

<select id="selectPageMap" resultType="map">
    select hid,title,type,page_views pageViews,TIMESTAMPDIFF(HOUR,create_time,NOW()) pastHours,
            publisher from news_headline where is_deleted=0
            <if test="portalVo.keyWords !=null and portalVo.keyWords.length()>0 ">
                and title like concat('%',#{portalVo.keyWords},'%')
            </if>
            <if test="portalVo.type != null and portalVo.type != 0">
                and type = #{portalVo.type}
            </if>
</select>

测试结果:

查询头条详情

1.需求:

        用户点击"查看全文"时,向服务端发送新闻id
        后端根据新闻id查询完整新闻文章信息并返回
        后端要同时让新闻的浏览量+1

2.接口描述:

url:portal/showHeadlineDetial

请求方式:POST

请求参数: param形式

hid=1

响应数据:

成功:

{
    "code":"200",
    "message":"success",
    "data":{
        "headline":{
            "hid":"1",                     // 新闻id 
            "title":"马斯克宣布 ... ...",   // 新闻标题
            "article":"... ..."            // 新闻正文
            "type":"1",                    // 新闻所属类别编号
            "typeName":"科技",             // 新闻所属类别
            "pageViews":"40",              // 新闻浏览量
            "pastHours":"3" ,              // 发布时间已过小时数
            "publisher":"1" ,              // 发布用户ID
            "author":"张三"                 // 新闻作者
        }
    }
}

3.代码实现:

controller:

 /**
 * 首页详情接口
 * @param hid
 * @return
 */
@PostMapping("showHeadlineDetail")
public Result showHeadlineDetail(Integer hid){
    Result result = headlineService.showHeadlineDetail(hid);
    return result;
}

service:

/**
 * 详情数据查询
 * "headline":{
 * "hid":"1",                     // 新闻id
 * "title":"马斯克宣布 ... ...",   // 新闻标题
 * "article":"... ..."            // 新闻正文
 * "type":"1",                    // 新闻所属类别编号
 * "typeName":"科技",             // 新闻所属类别
 * "pageViews":"40",              // 新闻浏览量
 * "pastHours":"3" ,              // 发布时间已过小时数
 * "publisher":"1" ,              // 发布用户ID
 * "author":"张三"                 // 新闻作者
 * }
 * 注意: 是多表查询 , 需要更新浏览量+1
 *
 * @param hid
 * @return
 */
@Override
public Result showHeadlineDetail(Integer hid) {

    //1.实现根据id的查询(多表
    Map headLineDetail = headlineMapper.selectDetailMap(hid);
    //2.拼接头条对象(阅读量和version)进行数据更新
    Headline headline = new Headline();
    headline.setHid(hid);
    headline.setPageViews((Integer) headLineDetail.get("pageViews")+1); //阅读量+1
    headline.setVersion((Integer) headLineDetail.get("version")); //设置版本
    headlineMapper.updateById(headline);

    Map<String,Object> pageInfoMap=new HashMap<>();
    pageInfoMap.put("headline",headLineDetail);
    return Result.ok(pageInfoMap);
}

mapper:

接口:

/**
 * 分页查询头条详情
 * @param hid
 * @return
 */
Map selectDetailMap(Integer hid);

mapper.xml:

<!--    Map selectDetailMap(Integer hid);-->
<select id="selectDetailMap" resultType="map">
    select hid,title,article,type, h.version ,tname typeName ,page_views pageViews
            ,TIMESTAMPDIFF(HOUR,create_time,NOW()) pastHours,publisher
                    ,nick_name author from news_headline h
                        left join news_type t on h.type = t.tid
                                left join news_user u  on h.publisher = u.uid
                                            where hid = #{hid}
</select>

测试结果:

头条模块开发

登录验证和保护

1.需求:

        客户端在进入发布页前、发布新闻前、进入修改页前、修改前、删除新闻前先向服务端发送请求携带token请求头
        后端接收token请求头后,校验用户登录是否过期并做响应
        前端根据响应信息提示用户进入登录页还是进入正常业务页面

2.接口描述:

url:user/checkLogin

请求方式:GET

请求参数:无

请求头:token: 用户token

3.代码实现:

controller:(登录后检查)

@GetMapping("checkLogin")
public Result checkLogin(@RequestHeader String token){
    if (StringUtils.isEmpty(token) || jwtHelper.isExpiration(token)){
        //没有传或者过期 未登录
        return Result.build(null, ResultCodeEnum.NOTLOGIN);
    }
    
    return Result.ok(null);
}

创建拦截器:

@Component
public class LoginProtectInterceptor implements HandlerInterceptor {

    @Autowired
    private JwtHelper jwtHelper;
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String token = request.getHeader("token");
        if (StringUtils.isEmpty(token) || jwtHelper.isExpiration(token)){
            Result result = Result.build(null, ResultCodeEnum.NOTLOGIN);
            ObjectMapper objectMapper = new ObjectMapper();
            String json = objectMapper.writeValueAsString(result);
            response.getWriter().print(json);
            //拦截
            return false;
        }else{
            //放行
            return true;
        }
    }
}

配置拦截器:(使用 /headline 开头都拦截)

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private LoginProtectInterceptor loginProtectInterceptor;

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

测试结果:

头条发布实现

1.需求:

        用户在客户端输入发布的新闻信息完毕后
        发布前先请求后端的登录校验接口验证登录
        登录通过则提交新闻信息
        后端将新闻信息存入数据库

2.接口描述:

url:headline/publish

请求方式:POST

请求头:token:用户token

请求参数:

{
    "title":"尚硅谷宣布 ... ...",   // 文章标题
    "article":"... ...",          // 文章内容
    "type":"1"                    // 文章类别
}

响应数据:

未登录:

{
    "code":"504",
    "message":"loginExpired",
    "data":{}
}

成功:

{
    "code":"200",
    "message":"success",
    "data":{}
}

3.代码实现:

controller:

/**
 * 实现步骤:
 *   1. token获取userId [无需校验,拦截器会校验]
 *   2. 封装headline数据
 *   3. 插入数据即可
 */
@PostMapping("publish")
public Result publish(@RequestBody Headline headline,@RequestHeader String token){

    int userId = jwtHelper.getUserId(token).intValue();
    headline.setPublisher(userId);
    Result result = headlineService.publish(headline);
    return result;
}

service:

/**
 * 发布数据
 * @param headline
 * @return
 */
@Override
public Result publish(Headline headline) {
    headline.setCreateTime(new Date());
    headline.setUpdateTime(new Date());
    headline.setPageViews(0);
    headlineMapper.insert(headline);
    return Result.ok(null);
}

测试结果:

修改头条回显

1.需求:

        前端先调用登录校验接口,校验登录是否过期

        登录校验通过后 ,则根据新闻id查询新闻的完整信息并响应给前端

2.接口描述:

url:headline/findHeadlineByHid

请求方式:POST

请求参数:param形式

hid=1

响应数据:

成功:

{
    "code":"200",
    "message":"success",
    "data":{
        "headline":{
            "hid":"1",
            "title":"马斯克宣布",
            "article":"... ... ",
            "type":"2"
        }
    }
}

3.代码实现:

controller:

@PostMapping("findHeadlineByHid")
public Result findHeadlineByHid(Integer hid){
    Result result = headlineService.findHeadlineByHid(hid);
    return result;
}

service:

/**
 * 根据id查询详情
 * @param hid
 * @return
 */
@Override
public Result findHeadlineByHid(Integer hid) {
    Headline headline = headlineMapper.selectById(hid);
    Map<String,Object> pageInfoMap=new HashMap<>();
    pageInfoMap.put("headline",headline);
    return Result.ok(pageInfoMap);
}

测试结果:

头条修改实现

1.需求:

        客户端将新闻信息修改后,提交前先请求登录校验接口校验登录状态
        登录校验通过则提交修改后的新闻信息,后端接收并更新进入数据库

2.接口描述:

url:headline/update

请求方式:POST

请求参数:

{
    "hid":"1",
    "title":"尚硅谷宣布 ... ...",
    "article":"... ...",
    "type":"2"
}

响应数据:

成功:

{
    "code":"200",
    "message":"success",
    "data":{}
}

3.代码实现:

controller:

@PostMapping("update")
public Result update(@RequestBody Headline headline){
    Result result = headlineService.updateHeadLine(headline);
    return result;
}

service:

 /**
 * 修改业务
 * 1.查询version版本
 * 2.补全属性,修改时间 , 版本!
 *
 * @param headline
 * @return
 */
@Override
public Result updateHeadLine(Headline headline) {

    //读取版本
    Integer version = headlineMapper.selectById(headline.getHid()).getVersion();

    headline.setVersion(version);
    headline.setUpdateTime(new Date());

    headlineMapper.updateById(headline);

    return Result.ok(null);
}

测试结果:

删除头条功能

1.需求:

        将要删除的新闻id发送给服务端
        服务端校验登录是否过期,未过期则直接删除,过期则响应登录过期信息

2.接口描述:

url:headline/removeByHid

请求方式:POST

请求参数:param形式

hid=1

响应数据:

成功:

{
    "code":"200",
    "message":"success",
    "data":{}
}

3.代码实现:

controller:

@PostMapping("removeByHid")
public Result removeById(Integer hid){
    headlineService.removeById(hid);
    return Result.ok(null);
}

测试结果:

 

  • 40
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值