【SpringBoot】头条新闻项目实现CRUD登录注册

文章目录

  • 一、头条案例介绍
  • 二、技术栈介绍
  • 三、前端搭建
  • 四、基于SpringBoot搭建项目基础架构
    • 4.1 数据库脚本执行
    • 4.2 搭建SprintBoot工程
      • 4.2.1 导入依赖:
      • 4.2.2 编写配置
      • 4.2.3 工具类准备
    • 4.3 MybatisX逆向工程
  • 五、后台功能开发
  • 5.1 用户模块开发
    • 5.1.1 jwt 和 token 介绍
    • 5.1.2 jwt 使用与测试
    • 5.1.3 用户登录接口实现
    • 5.1.4 根据token获取用户数据
    • 5.1.5 注册用户名占用情况检查
    • 5.1.6 用户注册
  • 5.2 首页模块开发
    • 5.2.1 查询首页分类
    • 5.2.2 *分页查询首页头条信息
    • 5.2.3 *查询头条详情 (多表
  • 5.3 头条模块开发
    • 5.3.1 登陆验证和保护
    • 5.3.2 头条发布实现
    • 5.3.3 修改头条回显
    • 5.3.4 头条修改实现
    • 5.3.5 删除头条功能
  • 总结


一、头条案例介绍

Github项目 : springboot-headline-part,新闻管理CRUD系统登录注册

  • 用户功能
    • 注册功能
    • 登录功能
    • 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

三、前端搭建

npm install 
npm run dev

四、基于SpringBoot搭建项目基础架构

4.1 数据库脚本执行

CREATE DATABASE sm_db;USE sm_db;SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;-- ----------------------------
-- Table structure for news_headline
-- ----------------------------
DROP TABLE IF EXISTS `news_headline`;
CREATE TABLE `news_headline`  (`hid` INT NOT NULL AUTO_INCREMENT COMMENT '头条id',`title` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '头条标题',`article` VARCHAR(5000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '头条新闻内容',`type` INT NOT NULL COMMENT '头条类型id',`publisher` INT NOT NULL COMMENT '头条发布用户id',`page_views` INT NOT NULL COMMENT '头条浏览量',`create_time` DATETIME(0) NULL DEFAULT NULL COMMENT '头条发布时间',`update_time` DATETIME(0) NULL DEFAULT NULL COMMENT '头条最后的修改时间',`version` INT DEFAULT 1 COMMENT '乐观锁',`is_deleted` INT DEFAULT 0 COMMENT '头条是否被删除 1 删除  0 未删除',PRIMARY KEY (`hid`) USING BTREE
) ENGINE = INNODB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC;-- ----------------------------
-- Table structure for news_type
-- ----------------------------
DROP TABLE IF EXISTS `news_type`;
CREATE TABLE `news_type`  (`tid` INT NOT NULL AUTO_INCREMENT COMMENT '新闻类型id',`tname` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '新闻类型描述',`version` INT DEFAULT 1 COMMENT '乐观锁',`is_deleted` INT DEFAULT 0 COMMENT '头条是否被删除 1 删除  0 未删除',PRIMARY KEY (`tid`) USING BTREE
) ENGINE = INNODB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC;-- ----------------------------
-- Records of news_type
-- ----------------------------
INSERT INTO `news_type` (tid,tname) VALUES (1, '新闻');
INSERT INTO `news_type` (tid,tname) VALUES (2, '体育');
INSERT INTO `news_type` (tid,tname) VALUES (3, '娱乐');
INSERT INTO `news_type` (tid,tname) VALUES (4, '科技');
INSERT INTO `news_type` (tid,tname) VALUES (5, '其他');-- ----------------------------
-- Table structure for news_user
-- ----------------------------
DROP TABLE IF EXISTS `news_user`;
CREATE TABLE `news_user`  (`uid` INT NOT NULL AUTO_INCREMENT COMMENT '用户id',`username` VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户登录名',`user_pwd` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户登录密码密文',`nick_name` VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户昵称',`version` INT DEFAULT 1 COMMENT '乐观锁',`is_deleted` INT DEFAULT 0 COMMENT '头条是否被删除 1 删除  0 未删除',PRIMARY KEY (`uid`) USING BTREE,UNIQUE INDEX `username_unique`(`username`) USING BTREE
) ENGINE = INNODB AUTO_INCREMENT = 9 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC;-- ----------------------------
-- Records of news_user
-- ----------------------------
INSERT INTO `news_user` (uid,username,user_pwd,nick_name) VALUES (1, 'zhangsan', 'e10adc3949ba59abbe56e057f20f883e', '张三');
INSERT INTO `news_user` (uid,username,user_pwd,nick_name) VALUES (2, 'lisi', 'e10adc3949ba59abbe56e057f20f883e', '李四');
INSERT INTO `news_user` (uid,username,user_pwd,nick_name) VALUES (5, 'zhangxiaoming', 'e10adc3949ba59abbe56e057f20f883e', '张小明');
INSERT INTO `news_user` (uid,username,user_pwd,nick_name)VALUES (6, 'xiaohei', 'e10adc3949ba59abbe56e057f20f883e', '李小黑');SET FOREIGN_KEY_CHECKS = 1;

4.2 搭建SprintBoot工程

  • 创建boot工程 : springboot-headline-part

4.2.1 导入依赖:

<?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.wake</groupId><artifactId>springboot-headline-part</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><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><!--    SpringBoot应用打包插件--><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>

4.2.2 编写配置

# server配置
server:port: 8080servlet:context-path: /# 连接池配置
spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedruid:url: jdbc:mysql:///sm_dbusername: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driver# mybatis-plus的配置
mybatis-plus:type-aliases-package: com.wake.pojoglobal-config:db-config:logic-delete-field: isDeleted  #全局逻辑删除id-type: auto #主键策略自增长table-prefix: news_ # 设置表的前缀
  • druid兼容springboot3文件
    • META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure
  • 启动类和mybatis-plus配置
@MapperScan("com.wake.mapper")
@SpringBootApplication
public class Main {public static void main(String[] args) {SpringApplication.run(Main.class,args);}@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(){MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();//分页mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));//防全局删除与更新mybatisPlusInterceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());//乐观锁mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());return mybatisPlusInterceptor;}
}

4.2.3 工具类准备

1

结果封装类

package com.wake.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;}
}

解决枚举类

package com.wake.utils;/*** 统一返回结果状态信息类*/
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加密工具类

package com.wake.utils;import org.springframework.stereotype.Component;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);}}
}

4.3 MybatisX逆向工程

1
1
1
完善实体类注解,注意yml已经全局配置的
主键注解、乐观锁、逻辑删除、版本Version

  • pojo实体类 属性ID 添加 @TableId
  • version版本属性 添加 @Version

五、后台功能开发

5.1 用户模块开发

5.1.1 jwt 和 token 介绍

1. token
令牌(Token):一种代表某种访问权限身份认证信息的令牌。

可以是一串随机生成的字符或数字,用于验证用户的身份或授权用户对特定资源的访问。

1

  • 每个用户生成的唯一字符串标识,可以进行用户识别和校验
  • token验证标识无法直接识别用户的信息,盗取token后也无法`登录`程序! 相对安全!
  • Token是一项规范和标准(接口)

2.jwt
JWT(JSON Web Token)是具体可以生成,校验,解析等动作Token的技术(实现类)
在这里插入图片描述

jwt 工作流程:

  • 用户提供其凭据(通常是用户名和密码)进行身份验证。
  • 服务器对这些凭据进行验证,并在验证成功后创建一个JWT。
  • 服务器将JWT发送给客户端,并客户端在后续的请求中将JWT附加在请求头或参数中。
  • 服务器接收到请求后,验证JWT的签名和有效性,并根据JWT中的声明进行身份验证和授权操作

jwt数据组成和包含信息:
JWT由三部分组成: header(头部).payload(载荷).signature(签名)
1 jwt可以携带很多信息! 一般情况,需要加入:有效时间,签名秘钥,其他用户标识信息!

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

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

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

5.1.2 jwt 使用与测试

  • 导入依赖
<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>
  • 配置yml
#jwt配置
jwt:token:tokenExpiration: 120 #有效时间,单位分钟tokenSignKey: headline123456  #当前程序签名秘钥 自定义      
  • 导入工具类
    封装jwt技术工具类
package com.wake.utils;import com.alibaba.druid.util.StringUtils;
import io.jsonwebtoken.*;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;import java.util.Date;@Data
@Component
@ConfigurationProperties(prefix = "jwt.token")
public class JwtHelper {private  long tokenExpiration; //有效时间,单位毫秒 1000毫秒 == 1秒private  String tokenSignKey;  //当前程序签名秘钥//生成token字符串public  String createToken(Long userId) {System.out.println("tokenExpiration = " + tokenExpiration);System.out.println("tokenSignKey = " + tokenSignKey);String token = Jwts.builder().setSubject("YYGH-USER").setExpiration(new Date(System.currentTimeMillis() + tokenExpiration*1000*60)) //单位分钟.claim("userId", userId).signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();return token;}//从token字符串获取useridpublic  Long getUserId(String token) {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());//没有过期,有效,返回falsereturn isExpire;}catch(Exception e) {//过期出现异常,返回truereturn true;}}
}
  • 测试
@SpringBootTest
public class SbTest {@Autowiredprivate JwtHelper jwtHelper;@Testpublic void test_jwt() {//生成 传入用户标识String token = jwtHelper.createToken(1L);System.out.println("token = " + token);//解析用户标识int userId = jwtHelper.getUserId(token).intValue();System.out.println("userId = " + userId);//校验是否到期! false 未到期 true到期boolean expiration = jwtHelper.isExpiration(token);System.out.println("expiration = " + expiration);}
}

1

5.1.3 用户登录接口实现

  1. 需求描述
    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":{}
}
  1. 代码实现
    controller:
@CrossOrigin
@RestController
@RequestMapping("user")
public class UserController {@Autowiredprivate UserService userService;@PostMapping("login")public Result login(@RequestBody User user){Result result = userService.login(user);return result;}
}

service:

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>implements UserService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate JwtHelper jwtHelper;@Overridepublic Result login(User user) {LambdaQueryWrapper<User> userLambdaQueryWrapper = new LambdaQueryWrapper<User>();userLambdaQueryWrapper.eq(User::getUsername, user.getUsername());User loginUser = userMapper.selectOne(userLambdaQueryWrapper);if (loginUser == null) {return Result.build(null, ResultCodeEnum.USERNAME_ERROR);}//对比密码if (!StringUtils.isEmpty(loginUser.getUserPwd())&& MD5Util.encrypt(user.getUserPwd()).equals(loginUser.getUserPwd())) {//根据用户ID生成token//并且将token封装进result返回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);}
}
  • 通过用户名查询 数据库中是否存在该User
    • 不存在,即返回501,输入的用户名有误
      • 需要直接判断整个User是否为空,因为查询返回的是一个User,而且是用 用户名查询数据库
      • 不要单独去判断用户名是否为空
    • 存在:即返回的User不为空,进入下一步 密码判断
  • 密码不为空且加密后密码对比 相等,允许登录且生成token
    1

5.1.4 根据token获取用户数据

  1. 需求描述
    客户端发送请求,提交token 请求头,后端根据token请求头获取登录用户的信息,并响应给用户。

此需求用于登陆之后,在前端页面可以获取使用用户信息,显示登陆信息的。像下列显示头像/用户名之类的…
1

  1. 接口描述
    url地址:user/getUserInfo
    请求方式:GET
    请求头:token: token内容

响应数据:

成功

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

失败

{"code": 504,"message": "notLogin","data": null
}
  1. 代码实现
    注意:实体类Id属性 要添加@TableId注解 指定ID,不然查询ID会报错,查不到ID
  • controller
    @GetMapping("getUserInfo")public Result getUserInfo(@RequestHeader String token){Result result = userService.getUserInfo(token);return result;}
  • service
    @Overridepublic Result getUserInfo(String token) {//判断token是否过期,TRUE到期,FALSE未到期if (jwtHelper.isExpiration(token)) {return Result.build(null,ResultCodeEnum.NOTLOGIN);}int userId = jwtHelper.getUserId(token).intValue();User user = userMapper.selectById(userId);user.setUserPwd("");Map map = new HashMap();map.put("loginUser",user);return Result.ok(map);}

先登录得到token,再查相应token。

  • 首先 判断token是否过期,过期登录失败
  • 在根据token获取uid(需要注解指定ID不然会有bug取不到id)
  • 根据ID获取user
  • 去掉密码内容
  • User封装map
    1

5.1.5 注册用户名占用情况检查

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

  2. 接口描述
    url地址:user/checkUserName
    请求方式:POST
    请求参数:param形式 : username=zhangsan

响应数据:

成功

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

失败

{"code":"505","message":"用户名占用""data":{}
}
  1. 代码实现

controller

    @PostMapping("checkUserName")public Result checkUserName(@RequestParam String username){Result result = userService.checkUserName(username);return result;}

service

userMapper.selectOne(userLambdaQueryWrapper);

    @Overridepublic Result checkUserName(String username) {LambdaQueryWrapper<User> userLambdaQueryWrapper = new LambdaQueryWrapper<>();userLambdaQueryWrapper.eq(User::getUsername,username);User user = userMapper.selectOne(userLambdaQueryWrapper);//不为空,用户已存在if(user != null){return Result.build(null,ResultCodeEnum.USERNAME_USED);}return Result.ok(null);}

userMapper.selectCount(userLambdaQueryWrapper);

    @Overridepublic Result checkUserName(String username) {LambdaQueryWrapper<User> userLambdaQueryWrapper = new LambdaQueryWrapper<>();userLambdaQueryWrapper.eq(User::getUsername,username);Long count = userMapper.selectCount(userLambdaQueryWrapper);//为0,没有数据if(count == 0){return Result.ok(null);}return Result.build(null,ResultCodeEnum.USERNAME_USED);}

1

5.1.6 用户注册

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

  2. 接口描述
    url地址:user/register
    请求方式:POST

请求参数:

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

响应数据:

成功

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

失败

{"code":"505","message":"用户名占用""data":{}
}
  1. 代码实现

注意:密码要MD5加密

controller

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

service

    /*** 注册* 1. 检查账号是否已被已被占用* 2. 密码加密MD5* 3. 账号数据保存insert* 4. 返回结果* @param user* @return*/@Overridepublic Result register(User user) {LambdaQueryWrapper<User> userLambdaQueryWrapper = new LambdaQueryWrapper<>();userLambdaQueryWrapper.eq(User::getUsername,user.getUsername());Long count = userMapper.selectCount(userLambdaQueryWrapper);if(count > 0){return Result.build(null,ResultCodeEnum.USERNAME_USED);}user.setUserPwd(MD5Util.encrypt(user.getUserPwd()));userMapper.insert(user);return Result.ok(null);}

1
1
1

5.2 首页模块开发

5.2.1 查询首页分类

  1. 需求描述
    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":"其他"}]}
}
  1. 代码实现
    controller
@RestController
@CrossOrigin
@RequestMapping("portal")
public class PortalController {@Autowiredprivate TypeService typeService;@GetMapping("findAllTypes")public Result findAllTypes(){Result result = typeService.findAllTypes();return result;}
}

service

@Service
public class TypeServiceImpl extends ServiceImpl<TypeMapper, Type>implements TypeService{@Autowiredprivate TypeMapper typeMapper;@Overridepublic Result findAllTypes() {List<Type> typeList = typeMapper.selectList(null);return Result.ok(typeList);}
}

1

5.2.2 *分页查询首页头条信息

  1. 需求:
    1
  • 客户端向服务端发送查询关键字,新闻类别,页码数,页大小
  • 服务端根据条件搜索分页信息,返回含页码数,页大小,总页数,总记录数,当前页数据等信息,并根据时间降序,浏览量降序排序
  1. 接口描述
    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 // 总记录数}}
}
  1. 代码实现
    请求参数 包装 Vo实体类
@Data
public class PortalVo {private String keyWords;private Integer type;private Integer pageNum = 1;private Integer pageSize =10;
}

controller

    @PostMapping("findNewsPage")public Result findNewsPage(@RequestBody PortalVo portalVo){Result result = headlineService.findNewsPage(portalVo);return result;}

service

@Service
public class HeadlineServiceImpl extends ServiceImpl<HeadlineMapper, Headline>implements HeadlineService{@Autowiredprivate HeadlineMapper headlineMapper;/*** 首页数据查询*  1. 进行分页数据查询*  2. 分页数据,拼接到result即可**  注意1 : 查询需要自定义语句,自定义Mapper的方法,携带分页*  注意2 : 返回结果List<Map>**      第一层pageInfo 由service层 IPage<Headline>整理完包装成pageInfoMap 返回给controller层*          第二层的pageData 自定义查询语句,包装成List<Headline>** @param portalVo* @return*/@Overridepublic Result findNewsPage(PortalVo portalVo) {//配置分页参数,当前页码数和当前页多少条IPage<Headline> page = new Page<>(portalVo.getPageNum(), portalVo.getPageSize());//自定义查询语句,portalVo传递keyword和type用于数据库查询headlineMapper.selectMyPage(page, portalVo);Map pageInfo = new HashMap();List<Headline> records = page.getRecords();pageInfo.put("pageData",records);pageInfo.put("pageNum",page.getCurrent());pageInfo.put("pageSize",page.getSize());pageInfo.put("totalPage",page.getPages());pageInfo.put("totalSize",page.getTotal());Map pageInfoMap = new HashMap();pageInfoMap.put("pageInfo",pageInfo);return Result.ok(pageInfoMap);}
}

mapper

    /*** 定义分页查询方法,返回map格式数据* @param page* @param portalVo* @return*/IPage<Headline> selectMyPage(IPage page, @Param("portalVo") PortalVo portalVo);

mapper.xml

    <select id="selectMyPage" 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

5.2.3 *查询头条详情 (多表

  1. 需求
    1
  • 用户点击"查看全文"时,向服务端发送新闻id
  • 后端根据新闻id查询完整新闻文章信息并返回
  • 后端要同时让新闻的浏览量+1
  1. 接口描述
    url地址:portal/showHeadlineDetail
    请求方式:post
    请求参数:
hid=1 param形成参数

响应数据:
成功

{"code":"200","message":"success","data":{"headline":{"hid":"1",                     // 新闻id "title":"马斯克宣布 ... ...",   // 新闻标题"article":"... ..."            // 新闻正文"type":"1",                    // 新闻所属类别编号"typeName":"科技",             // 新闻所属类别"pageViews":"40",              // 新闻浏览量"pastHours":"3" ,              // 发布时间已过小时数"publisher":"1" ,              // 发布用户ID"author":"张三"                 // 新闻作者}}
}
  1. 代码实现
    controller
    @PostMapping("showHeadlineDetail")public Result showHeadlineDetail(@RequestParam String hid){Result result = headlineService.showHeadlineDetail(hid);return result;}

service

    /*** 根据id查询详情* 1. 查询对应的数据即可【多表查询,头条和用户表,自定义Mapper方法 返回map】* 2. 修改阅读量 【 "pageViews":"40",   // 新闻浏览量; 涉及到乐观锁需要当前数据对应版本】** @param hid* @return*/@Overridepublic Result showHeadlineDetail(String hid) {Map data = headlineMapper.selectDetailByHidMap(hid);HashMap headlineMap = new HashMap();headlineMap.put("headline", data);// 修改阅读量+1,version乐观锁Headline headline = new Headline();headline.setHid((Integer) data.get("hid"));headline.setVersion((Integer) data.get("version"));headline.setPageViews((Integer) data.get("pageViews") + 1);headlineMapper.updateById(headline);return Result.ok(headlineMap);}

mapper

    /*** 多表查询,新闻详情* @param hid* @return*/Map selectDetailByHidMap(String hid);

mapper.xml

    <select id="selectDetailByHidMap" 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 hleft join news_type t on h.type = t.tidleft join news_user u  on h.publisher = u.uidwhere hid = #{hid}</select>

1

5.3 头条模块开发

5.3.1 登陆验证和保护

  1. 需求
    1
  • 客户端在进入发布页前、发布新闻前、进入修改页前、修改前、删除新闻前先向服务端发送请求携带token请求头
  • 后端接收token请求头后,校验用户登录是否过期并做响应
  • 前端根据响应信息提示用户进入登录页还是进入正常业务页面
  1. 接口描述
    url地址:user/checkLogin
    请求方式:get
    请求参数: 无
    请求头: token: 用户token

响应数据:

未过期:

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

过期:

{"code":"504","message":"loginExpired","data":{}
}
  1. 代码实现
    controller : 【登录检查】
    UserController
    @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);}

拦截器 【所有/headline 开头的都需要检查登录状态】
package com.wake.interceptors;

/*** @Description: 登录包含拦截器,检查请求头是否包含有效token* 有 有效 放行* 没有 无效 返回504*/
@Component
public class LoginProtectedInterceptor implements HandlerInterceptor {@Autowiredprivate JwtHelper jwtHelper;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 请求头从获取tokenString token = request.getHeader("token");// 检查是否有效if (StringUtils.isEmpty(token) || jwtHelper.isExpiration(token)) {// 无效返回504的状态jsonResult<Object> result = Result.build(null, ResultCodeEnum.NOTLOGIN);// 能将result对象转为字符串ObjectMapper objectMapper = new ObjectMapper();String json = objectMapper.writeValueAsString(result);response.getWriter().print(json);return false;}// 有效放行return true;}
}

拦截器配置
在这里插入图片描述

/*** @Description: 添加拦截器*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {@Autowiredprivate LoginProtectedInterceptor loginProtectedInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginProtectedInterceptor).addPathPatterns("/headline/**");}
}

5.3.2 头条发布实现

  1. 需求
    1
  • 用户在客户端输入发布的新闻信息完毕后
  • 发布前先请求后端的登录校验接口验证登录
  • 登录通过则提交新闻信息
  • 后端将新闻信息存入数据库
  1. 接口描述
    url地址:headline/publish
    请求方式:post
    请求头 : token: … …

请求参数:

{"title":"文章标题 ... ...",   // 文章标题"article":"... 文章内容 ...",          // 文章内容"type":"1"                    // 文章类别
}

响应数据:

未登录

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

成功

{"code":"200","message":"success","data":{}
}
  1. 代码实现
    controller
@RestController
@CrossOrigin
@RequestMapping("headline")
public class HeadlineController {@Autowiredprivate HeadlineService headlineService;/*** 登录后才能 发布新闻* @param headline* @param token* @return*/@PostMapping("publish")public Result publish(@RequestBody Headline headline,@RequestHeader String token){Result result = headlineService.publish(headline,token);return result;}
}

service
headline

    @Overridepublic Result publish(Headline headline, String token) {// token 查询用户idint userId = jwtHelper.getUserId(token).intValue();headline.setPublisher(userId);headline.setPageViews(0);headline.setCreateTime(new Date());headline.setUpdateTime(new Date());headlineMapper.insert(headline);return Result.ok(null);}

1

5.3.3 修改头条回显

  1. 需求
    1
  • 前端先调用登录校验接口,校验登录是否过期
  • 登录校验通过后 ,则根据新闻id查询新闻的完整信息并响应给前端
  1. 接口描述
    url地址:headline/findHeadlineByHid
    请求方式:post

请求参数:

hid=1 param形成参数

响应数据:
成功

{"code":"200","message":"success","data":{"headline":{"hid":"1","title":"马斯克宣布","article":"... ... ","type":"2"}}
}
  1. 代码实现
    controller
    也可以直接调用service的继承的方法
    @PostMapping("findHeadlineByHid")public Result findHeadlineByHid(@RequestParam String hid) {Headline headline = headlineService.getById(hid);Map map = new HashMap();map.put("headline", headline);return Result.ok(map);}

先登录,拿到token
1

5.3.4 头条修改实现

  1. 需求
  • 客户端将新闻信息修改后,提交前先请求登录校验接口校验登录状态
  • 登录校验通过则提交修改后的新闻信息,后端接收并更新进入数据库
  1. 接口描述
    url地址:headline/update
    请求方式:post

请求参数:

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

响应数据:
成功

{"code":"200","message":"success","data":{}
}
  1. 代码实现

controller

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

service
直接更新前需要设置version乐观锁 以及 最新时间节点
然后再更新

    /*** 修改头条新闻* 1. hid查询数据的最新version* 2. 修改数据的更新时间为当前节点* @param headline* @return*/@Overridepublic Result updateData(Headline headline) {Integer version = headlineMapper.selectById(headline.getHid()).getVersion();//乐观锁headline.setVersion(version);headline.setUpdateTime(new Date());headlineMapper.updateById(headline);return Result.ok(null);}

1

5.3.5 删除头条功能

  1. 需求
    在这里插入图片描述- 将要删除的新闻id发送给服务端
  • 服务端校验登录是否过期,未过期则直接删除,过期则响应登录过期信息
  1. 接口描述
    url地址:headline/removeByHid
    请求方式:post

请求参数 : hid=1 param形成参数

响应数据:成功

{"code":"200","message":"success","data":{}
}
  1. 代码实现
    controller
    @PostMapping("removeByHid")public Result removeByHid(@RequestParam String hid){headlineService.removeById(hid);return Result.ok(null);}

1
1


总结

SSM官方笔记
1

头条新闻纯JavaWeb项目实现 1
JavaWeb_头条新闻项目实现 2

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/747988.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

huawei services HK华为云服务

huaweiserviceshk是一种云计算服务&#xff0c;为华为云服务用户提供了多种服务&#xff0c;包括云服务器、数据库、存储、网络等&#xff0c;用户可以根据自己的需求选择不同的服务并支付相应的费用 如何付费呢&#xff0c;这里可以使用441112&#xff0c;点击获取 卡片信息在…

springboot+poi-tl根据模板导出word(含动态表格和图片),并将导出的文档压缩zip导出

springbootpoi-tl根据模板导出word&#xff08;含动态表格和图片&#xff09; 官网&#xff1a;http://deepoove.com/poi-tl/ 参考网站&#xff1a;https://blog.csdn.net/M625387195/article/details/124855854 pom导入的maven依赖 <dependency><groupId>com.dee…

基于openCV实现的单目相机行人和减速带检测

概述 在计算机视觉项目中&#xff0c;相机标定是一项至关重要的任务&#xff0c;因为它可以校正相机内部参数&#xff0c;消除因镜头畸变等因素导致的图像失真&#xff0c;从而提高后续图像处理和分析的精度。在这个项目中&#xff0c;相机标定的核心功能集成在名为calibratio…

还原wps纯粹的编辑功能

1.关闭稻壳模板&#xff1a; 1.1. 启动wps(注意不要乱击稻壳模板&#xff0c;点了就找不到右键菜单了) 1.2. 在稻壳模板选项卡右击&#xff1a;选不再默认展示 2.关闭托盘中wps云盘图标&#xff1a;右击云盘图标/同步与设置&#xff1a; 2.1.关闭云文档同步 2.2.窗口选桌面应用…

Vue2+ElementUI表单、Form组件的封装

Vue2ElementUI表单、Form组件的封装 &#xff1a;引言 在 Vue2 项目中&#xff0c;ElementUI 的 el-form 组件是常用的表单组件。它提供了丰富的功能和样式&#xff0c;可以满足各种需求。但是&#xff0c;在实际开发中&#xff0c;我们经常会遇到一些重复性的需求&#xff0c…

16.WEB渗透测试--Kali Linux(四)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;15.WEB渗透测试--Kali Linux&#xff08;三&#xff09;-CSDN博客 1.crunch简介与使用 C…

分布式CAP理论

CAP理论&#xff1a;一致性&#xff08;Consistency&#xff09;、可用性&#xff08;Availability&#xff09;和分区容错性&#xff08;Partition tolerance&#xff09;。是Eric Brewer在2000年提出的&#xff0c;用于描述分布式系统基本性质的定理。这三个性质在分布式系统…

FPGA静态时序分析与约束(一)、理解亚稳态

系列文章目录 FPGA静态时序分析与约束&#xff08;二&#xff09;、时序分析 FPGA静态时序分析与约束&#xff08;三&#xff09;、读懂vivado时序报告 文章目录 系列文章目录前言一、概述一、何为亚稳态&#xff1f;二、图解亚稳态三、什么时候亚稳态会导致系统失效&#xff…

k8s部署hadoop

&#xff08;作者&#xff1a;陈玓玏&#xff09; 配置和模板参考helm仓库&#xff1a;https://artifacthub.io/packages/helm/apache-hadoop-helm/hadoop 先通过以下命令生成yaml文件&#xff1a; helm template hadoop pfisterer-hadoop/hadoop > hadoop.yaml用kube…

Unity PS5开发 天坑篇 之 申请开发者与硬件部署01

腾了好几天终于把PS5开发机调试部署成功, 希望能帮到国内的开发者, 主机游戏PlayStation/Nintendo Switch都是比较闭塞的&#xff0c;开发者账号是必须的。 开发环境有两个部分&#xff0c;一是DEV Kit 开发机, TEST Kit测试机两部分组成&#xff0c;二是Unity的支持库(安装后…

最新开源解密版TwoNav网址导航系统源码

源码简介 2024最新开源解密版TwoNav网址导航系统源码去授权破解版 内置二十多套主题模板。 已去授权&#xff0c;最新开源解密版。TwoNav 是一款开源的书签&#xff08;导航&#xff09;管理程序&#xff0c;使用PHP SQLite 3开发&#xff0c;界面简洁&#xff0c;安装简单&…

FFmepg--音频编码流程--pcm编码为aac

文章目录 基本概念流程apicode(核心部分) 基本概念 从本地⽂件读取PCM数据进⾏AAC格式编码&#xff0c;然后将编码后的AAC数据存储到本地⽂件。 PCM样本格式&#xff1a;未经压缩的⾳频采样数据裸流 参数&#xff1a; Sample Rate : 采样频率Sample Size : 量化位数Number o…

Matlab进阶绘图第45期—蝴蝶气泡图

蝴蝶气泡图是一种特殊的柱泡图/气泡柱状图。 蝴蝶图一般由左右两个水平柱状图组合而成&#xff0c;其形如蝴蝶展翅&#xff0c;可以很直观地展示两种数据直接的差异。 而蝴蝶气泡图则是在两个水平柱状图每根柱子外侧额外添加大小不同的气泡&#xff0c;用于表示另外一个数据变…

使用IDEA2023创建传统的JavaWeb项目并运行与调试

日期:2024-0312 作者:dusuanyun 文档环境说明: OS:Deepin 20.9(Linux) JDK: OpenJDK21 Tomcat:10.1.19 IDEA: 2023.3.4 (Ultimate Edition) 本文档默认已经安装JDK及环境变量的配置。 关键词…

单片机设计-超声波视力保护仪的设计与实现

项目介绍 技术&#xff1a;C语言、单片机等 本设计利用超声波技术检测眼睛与书本的距离&#xff0c;调整看书位置&#xff0c;通过光敏检测判断环境光线强度是否适合阅读&#xff0c;并通过定时器设定阅读时长&#xff0c;以此解决人们由于看书姿势的错误&#xff0c;阅读环境…

R语言数据挖掘-关联规则挖掘(1)

一、分析目的和数据集描述 要分析的数据是美国一区域的保险费支出的历史数据。保险费用数据表的每列分别为年龄、性别、体重指数、孩子数量、是否吸烟、所在区域、保险收费。 本文的主要目的是分析在年龄、性别、体重指数、孩子数量、是否吸烟、所在区域中这些因素中&#xf…

webpack5零基础入门-8清空前次打包文件与处理图标字体资源

1.配置output中的clean属性为true output: {/**文件输出路径 绝对路径*///__dirname 表示当前文件的文件夹目录path: path.resolve(__dirname, dist),//所有文件的输出目录/**文件名 */filename: static/js/dist.js,//入口文件输出文件名clean: true,//在打包前将path整个目录内…

SSM SpringBoot vue智能手机参数分析平台

SSM SpringBoot vue智能手机参数分析平台 系统功能 首页 图片轮播 新闻资讯 手机信息 手机百科 登录注册 个人中心 后台管理 登录注册 个人中心 手机百科管理 用户管理 手机对比管理 配置管理 新闻资讯管理 手机信息管理 对比信息管理 我的收藏管理 开发环境和技术 开发语言…

安卓国产百度网盘与国外云盘软件onedrive对比

我更愿意使用国外软件公司的产品&#xff0c;而不是使用国内百度等制作的流氓软件。使用这些国产软件让我不放心&#xff0c;他们占用我的设备大量空间&#xff0c;在我的设备上推送运行各种无用的垃圾功能。瞒着我&#xff0c;做一些我不知道的事情。 百度网盘安装包大小&…

爬虫 某物流

目标地址 url "https://api.jdl.com/aging/feeInquiryNewByJDL" 加密参数 ciphertext和data 搜关键字ciphertext跟着栈走 很明显的DES加密 window globalconst e require(jsencrypt); // const e require(JSEncrypt) // e r(775).JSEncrypt // const t requi…