SpringBoot微版头条
SpringBoot版微头条实战
一、微头条案例介绍
微头条业务简介
- 用户功能
- 注册功能
- 登录功能
- 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
二、微头条前端搭建
确保本地node,npm,vscode安装完毕!
下载前端代码my-vue3-vite-project
使用VsCode打开上面的工程
通过 npm run dev启动前端项目
npm install
npm run dev
三、基于SpringBoot搭建项目基础结构
3.1 数据库脚本执行
执行数据库脚本:top_news
3.2 搭建SpringBoot工程
-
导入依赖
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.0.5</version> </parent> <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.22</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> <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> </dependencies> <!-- SpringBoot应用打包插件--> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> -
编写
application.yaml配置文件# server配置 server: port: 8080 servlet: context-path: / # 连接池配置 spring: datasource: type: com.alibaba.druid.pool.DruidDataSource druid: url: jdbc:mysql://localhost:3306/sm_db?characterEncoding=utf-8 username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver # mybatis-plus的配置 mybatis-plus: type-aliases-package: com.yue.pojo global-config: db-config: logic-delete-field: isDeleted #全局逻辑删除 id-type: auto #主键策略自增长 table-prefix: news_ # 设置表的前缀 #jwt配置 jwt: token: tokenExpiration: 120 #有效时间,单位分钟 tokenSignKey: headline123456 #当前程序签名秘钥 自定义 -
启动类和mybatis-plus配置
包:com.yue
@SpringBootApplication @MapperScan("com.yue.mapper") public class Main { public static void main(String[] args) { SpringApplication.run(Main.class,args); } //配置mybatis-plus插件 @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); //分页 interceptor.addInnerInterceptor( new PaginationInnerInterceptor(DbType.MYSQL)); //乐观锁 interceptor.addInnerInterceptor( new OptimisticLockerInnerInterceptor()); //防全局修改和删除 interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor()); return interceptor; } } -
工具类准备
一、工具类准备
/** * 全局统一返回结果类 */ 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); } } }
3.3 MyBatisX逆向工程
IDEA连接数据库



完善实体类注解
删除@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;
}
四、后台功能开发
4.1 用户模块开发
4.1.1 jwt和token介绍
token介绍
令牌(Token):在计算机领域,令牌是一种代表某种访问权限或身份认证信息的令牌。它可以是一串随机生成的字符或数字,用于验证用户的身份或授权用户对特定资源的访问。普通的令牌可能以各种形式出现,如访问令牌、身份令牌、刷新令牌等。

简单理解 : 每个用户生成的唯一字符串标识,可以进行用户识别和校验
优势: token验证标识无法直接识别用户的信息,盗取token后也无法登录程序! 相对安全!
jwt介绍
Token是一项规范和标准(接口)
JWT(JSON Web Token)是具体可以生成,校验,解析等动作Token的技术(实现类)

-
jwt工作流程
- 用户提供其凭据(通常是用户名和密码)进行身份验证。
- 服务器对这些凭据进行验证,并在验证成功后创建一个JWT。
- 服务器将JWT发送给客户端,并客户端在后续的请求中将JWT附加在请求头或参数中。
- 服务器接收到请求后,验证JWT的签名和有效性,并根据JWT中的声明进行身份验证和授权操作
-
jwt数据组成和包含信息
JWT由三部分组成: header(头部).payload(载荷).signature(签名)

我们需要理解的是, jwt可以携带很多信息! 一般情况,需要加入:有效时间,签名秘钥,其他用户标识信息!
有效时间为了保证token的时效性,过期可以重新登录获取!
签名秘钥为了防止其他人随意解析和校验token数据!
用户信息为了我们自己解析的时候,知道Token对应的具体用户!
-
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>
-
编写配置
application.yaml
#jwt配置
jwt:
token:
tokenExpiration: 120 #有效时间,单位分钟
tokenSignKey: headline123456 #当前程序签名秘钥 自定义
-
导入工具类
封装jwt技术工具类
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字符串获取userid
public 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());
//没有过期,有效,返回false
return isExpire;
}catch(Exception e) {
//过期出现异常,返回true
return true;
}
}
}
4. 使用和测试
@org.springframework.boot.test.context.SpringBootTest
public class SpringBootTest {
@Autowired
private JwtHelper jwtHelper;
@Test
public void test(){
//生成 传入用户标识
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);
}
}
4.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":{}
}
三、实现代码
-
controller
@RestController @RequestMapping("user") @CrossOrigin public class UserController { @Autowired private UserService userService; /** 大概流程: * 1. 账号进行数据库查询 返回用户对象 * 2. 对比用户密码(md5加密) * 3. 成功,根据userId生成token -> map key=token value=token值 - result封装 * 4. 失败,判断账号还是密码错误,封装对应的枚举错误即可 */ @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{ @Autowired private UserMapper userMapper; @Autowired private JwtHelper jwtHelper; /** * 登录业务实现 * 1.根据账号查询 * 2.账号判断 * 3.密码判断 * @param user * @return result封装 */ @Override public Result login(User user) { // 1.根据账号查询 LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>(); lambdaQueryWrapper.eq(User::getUsername,user.getUsername()); User loginUser = userMapper.selectOne(lambdaQueryWrapper); // 2.账号判断 if (loginUser == null){ // 账号错误 return Result.build(null, ResultCodeEnum.USERNAME_ERROR); } // 3.密码判断 if (!StringUtils.isEmpty(user.getUserPwd()) && loginUser.getUserPwd().equals(MD5Util.encrypt(user.getUserPwd()))) { // 密码正确 // 根据用户唯一标识生成token String token = jwtHelper.createToken(Long.valueOf(loginUser.getUid())); // 根据获取格式,需要放到map集合里 Map data = new HashMap(); data.put("token",token); return Result.ok(data); } // 密码错误 return Result.build(null,ResultCodeEnum.PASSWORD_ERROR); } }
4.1.3 根据token获取用户数据
一、需求描述
客户端发送请求,提交token请求头,后端根据token请求头获取登录用户的详细信息并响应给客户端进行存储
二、接口描述
- 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
/** * 获取token对应用户 * 大概流程: * 1.获取token,解析token对应的userId * 2.根据userId,查询用户数据 * 3.将用户数据的密码置空,并且把用户数据封装到结果中key = loginUser * 4.失败返回504 (本次先写到当前业务,后期提取到拦截器和全局异常处理器) */ @GetMapping("getUserInfo") public Result getUserInfo(@RequestHeader String token){ Result result = userService.getUserInfo(token); return result; } -
service
/** * 获取token对应用户数据 * 1.判断token是否过期 * 2.token未过期,获取token对应的用户 * 3.查询数据 * @param token * @return result封装 */ @Override public Result getUserInfo(String token) { // 1.判断token是否过期 if (jwtHelper.isExpiration(token)){ // true过期,直接返回未登陆 return Result.build(null,ResultCodeEnum.NOTLOGIN); } // 2.token未过期,获取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); }
4.1.4 注册用户名检查是否存在
一、需求描述
用户在注册时输入用户名时,立刻将用户名发送给后端,后端根据用户名查询用户名是否可用并做出响应
二、接口描述
- url地址:
user/checkUserName - 请求方式:
POST - 请求参数:
param形式
username=zhangsan
-
响应数据:
-
成功
{
"code":"200",
"message":"success"
"data":{}
}
- 失败
{
"code":"505",
"message":"用户名占用"
"data":{}
}
三、代码实现
-
controller
/** * 检查注册用户名是否可用 * 实现步骤: * 1. 获取账号数据 * 2. 根据账号进行数据库查询 * 3. 结果封装 */ @PostMapping("checkUserName") public Result checkUserName(String username){ Result result = userService.checkUserName(username); return result; } -
service
/** * 检查账号是否可以注册 * 1.获取账号数据 * 2.根据账号进行数据库查询 * 3.结果封装 * @param username 账号信息 * @return */ @Override public Result checkUserName(String username) { // 1.获取账号数据 LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>(); lambdaQueryWrapper.eq(User::getUsername,username); // 2.根据账号进行数据库查询 User user = userMapper.selectOne(lambdaQueryWrapper); // 3.结果封装 if (user != null){ // 如果用户名存在,则不能注册 return Result.build(null,ResultCodeEnum.USERNAME_USED); } // 用户名不存在,可以注册 return Result.ok(null); }
4.1.5 用户注册功能
一、需求描述
客户端将新用户信息发送给服务端,服务端将新用户存入数据库,存入之前做用户名是否被占用校验,校验通过响应成功提示,否则响应失败提示
二、接口描述
- url地址:
user/regist - 请求方式:
POST - 请求参数:
{
"username":"zhangsan",
"userPwd":"123456",
"nickName":"张三"
}
-
响应数据:
-
成功
{
"code":"200",
"message":"success"
"data":{}
}
- 失败
{
"code":"505",
"message":"用户名占用"
"data":{}
}
三、代码实现
-
controller
/** * 用户注册功能 * 实现步骤: * 1. 将密码加密 * 2. 将数据插入 * 3. 判断结果,成 返回200 失败 505 */ @PostMapping("regist") public Result regist(@RequestBody User user){ Result result = userService.regist(user); return result; } -
service
/** * 注册业务 * 1.检查账号是否已经注册 * 2.密码加密处理 * 3.账号数据保存 * 4.返回结果 * @param user * @return */ @Override public Result regist(User user) { // 1.检查账号是否已经注册 LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>(); lambdaQueryWrapper.eq(User::getUsername,user.getUsername()); User loginUser = userMapper.selectOne(lambdaQueryWrapper); if (loginUser != null){ return Result.build(null,ResultCodeEnum.USERNAME_USED); } // 2.密码加密处理 user.setUserPwd(MD5Util.encrypt(user.getUserPwd())); // 3.账号数据保存 userMapper.insert(user); return Result.ok(null); }
4.2 首页模块开发
4.2.1 查询首页分类
一、需求描述

进入新闻首页,查询所有分类并动态展示新闻类别栏位
二、接口描述
-
url地址:
portal/findAllTypes -
请求方式:
Get -
请求参数:无
-
响应数据:
-
成功
{
"code":"200",
"message":"OK"
"data":{
[
{
"tid":"1",
"tname":"新闻"
},
{
"tid":"2",
"tname":"体育"
},
{
"tid":"3",
"tname":"娱乐"
},
{
"tid":"4",
"tname":"科技"
},
{
"tid":"5",
"tname":"其他"
}
]
}
}
三、代码实现
-
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); } }
4.2.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 // 总记录数
}
}
}
三、代码实现
-
准备条件实体类
@Data public class PortalVo { private String keyWords; private Integer type; private Integer pageNum = 1; private Integer pageSize =10; } -
自定义分页查询mapper方法
public interface HeadlineMapper extends BaseMapper<Headline> { // 自定义分页查询方法 IPage<Map> selectPageMap(IPage<Headline> page, @Param("portalVo") PortalVo portalVo); } -
HeadlineMapper.xml编写sql语句
<select id="selectPageMap" resultType="map"> select hid,title,type,page_views, 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 != 0"> and type = #{portalVo.type} </if> </select> -
controller
/** * 首页分页查询 */ @PostMapping("findNewPage") public Result findNewsPage(@RequestBody PortalVo portalVo){ Result result = headlineService.findNewPage(portalVo); return result; } -
service
/** * @description 针对表【news_headline】的数据库操作Service实现 */ @Service public class HeadlineServiceImpl extends ServiceImpl<HeadlineMapper, Headline> implements HeadlineService{ // 自动装备HeadlineMapper @Autowired private HeadlineMapper headlineMapper; /** * 首页数据查询 * 1.条件拼接 需要非空判断 * 2.分页参数 * 3.分页查询 * 4.分页数据封装 * 注意:1.查询需要自定义语句,自定义mapper方法,携带分页 * 2.返回结果为List<map>,即数组里嵌套map集合 * @param portalVo * @return */ @Override public Result findNewsPage(PortalVo portalVo) { // 1.条件拼接 需要非空判断 LambdaQueryWrapper<Headline> lambdaQueryWrapper = new LambdaQueryWrapper<>(); lambdaQueryWrapper.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); } }
4.2.3 查询头条详情
一、需求描述
- 用户点击"查看全文"时,向服务端发送新闻id
- 后端根据新闻id查询完整新闻文章信息并返回
- 后端要同时让新闻的浏览量+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":"张三" // 新闻作者
}
}
}
三、代码实现
-
controller
/** * 首页详情接口 * @param hid * @return */ @PostMapping("showHeadlineDetail") public Result showHeadlineDetail(Integer hid){ Result result = headlineService.showHeadlineDetail(hid); return result; } -
service
/** * 详情数据查询 * 1.实现根据id的查询(多表查询) * 2.拼接头条对象(阅读量和version)进行数据更新 * 注意: 是多表查询 , 需要更新浏览量+1 */ @Override public Result showHeadlineDetail(Integer hid) { // 1.实现根据id的查询(多表查询) Map headLineDetail = headlineMapper.selectDelailMap(hid); // 2.拼接头条对象(阅读量和version)进行数据更新 Headline headline = new Headline(); headline.setHid(hid); // 设置阅读量 + 1 headline.setPageViews((Integer) headLineDetail.get("pageViews") + 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); -
HeadlineMapper.xml添加sql语句
<select id="selectDelailMap" resultType="map"> select hid,title,article,type,page_views pageViews, TIMESTAMPDIFF(HOUR,create_time,NOW()) pastHours,publisher,h.version, tname typeName,nick_name author from news_headline h left join news_type t on t.tid = h.type left join news_user u on u.uid = h.publisher where hid = #{hid} </select>
4.3 头条模块开发
4.3.1 登陆认证与授权
一、需求描述

- 客户端在进入发布页前、发布新闻前、进入修改页前、修改前、删除新闻前先向服务端发送请求携带
token请求头 - 后端接收
token请求头后,校验用户登录是否过期并做响应 - 前端根据响应信息提示用户进入登录页还是进入正常业务页面
二、接口描述
- url地址:
user/checkLogin - 请求方式:
get - 请求参数: 无
- 请求头: token:用户的token
- 响应数据:
- 未过期:
{
"code":"200",
"message":"success",
"data":{}
}
- 过期:
{
"code":"504",
"message":"loginExpired",
"data":{}
}
三、代码实现
-
controller 【登录检查】防止token过期还能还能使用功能
/** * 登陆检查,防止token过期 */ @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开头都需要检查登陆】/** * Description:登录拦截器 * 检查请求头是否包含有token * 有并且有效,放行 * 无,拦截,返回504 */ @Component public class LoginProtectInterceptor implements HandlerInterceptor { @Autowired private JwtHelper jwtHelper; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 从请求头中获取token String token = request.getHeader("token"); // 检查token是否有效 if(StringUtils.isEmpty(token) || jwtHelper.isExpiration(token)){ // 没有或过期拦截 Result result = Result.build(null, ResultCodeEnum.NOTLOGIN); // 将结果使用ObjectMapper转化为json字符串 ObjectMapper objectMapper = new ObjectMapper(); String json = objectMapper.writeValueAsString(result); // 响应返回json数据 response.getWriter().print(json); return false; } return true; } } -
拦截器配置
/** * Description:拦截器配置类 */ @Configuration public class WebMvcConfig implements WebMvcConfigurer { @Autowired private LoginProtectInterceptor loginProtectInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginProtectInterceptor) .addPathPatterns("/headline/**"); } }
4.3.2 头条发布实现
一、需求描述

- 用户在客户端输入发布的新闻信息完毕后
- 发布前先请求后端的登录校验接口验证登录
- 登录通过则提交新闻信息
- 后端将新闻信息存入数据库
二、接口描述
-
url地址:
headline/publish -
请求方式:
post -
请求头:token:... ...
-
请求参数:
{
"title":"尚硅谷宣布 ... ...", // 文章标题
"article":"... ...", // 文章内容
"type":"1" // 文章类别
}
- 响应数据:
- 未登录
{
"code":"504",
"message":"loginExpired",
"data":{}
}
- 成功
{
"code":"200",
"message":"success",
"data":{}
}
三、代码实现
-
controller
/** * 头条发布 * 实现步骤: * 1. 根据token获取userId [无需校验,拦截器会校验] * 2. 封装headline数据 * 3. 插入数据即可 */ @PostMapping("publish") public Result publish(@RequestBody Headline headline, @RequestHeader String token){ Result result = headlineService.publish(headline,token); return result; } -
service
/** * 头条发布 * 实现步骤: * 1. 根据token获取userId [无需校验,拦截器会校验] * 2. 补全headlin数据 * 3. 封装headline数据 * 4. 插入数据即可 */ @Override public Result publish(Headline headline, String token) { // 1.根据token获取userId [无需校验,拦截器会校验] int userId = jwtHelper.getUserId(token).intValue(); headline.setPublisher(userId); // 2.补全headlin数据 headline.setPageViews(0); headline.setCreateTime(new Date()); headline.setUpdateTime(new Date()); // 3.封装headline数据 headlineMapper.insert(headline); return Result.ok(null); }
4.3.3 修改头条回显
一、需求描述

- 前端先调用登录校验接口,校验登录是否过期
- 登录校验通过后 ,则根据新闻id查询新闻的完整信息并响应给前端
二、接口描述
- url地址:
headline/findHeadlineByHid - 请求方式:
Post - 请求参数:
hid=1 param形成参数
- 响应数据:
- 成功
{
"code":"200",
"message":"success",
"data":{
"headline":{
"hid":"1",
"title":"马斯克宣布",
"article":"... ... ",
"type":"2"
}
}
}
三、代码实现
-
controller
/** * 修改头条回显 */ @PostMapping ("findHeadlineByHid") public Result findHeadlineByHid(Integer hid){ Result result = headlineService.findHeadlineByHid(hid); return result; } -
service
/** * 修改头条回显 * 1.根据id查询回显 */ @Override public Result findHeadlineByHid(Integer hid) { // 1.根据id查询回显 Headline headline = headlineMapper.selectById(hid); Map<String,Object> pageInfoMap = new HashMap<>(); pageInfoMap.put("headline",headline); return Result.ok(pageInfoMap); }
4.3.4 头条修改实现
一、需求描述
- 客户端将新闻信息修改后,提交前先请求登录校验接口校验登录状态
- 登录校验通过则提交修改后的新闻信息,后端接收并更新进入数据库
二、接口描述
- url地址:
headline/update - 请求方式:
post - 请求参数:
{
"hid":"1",
"title":"尚硅谷宣布 ... ...",
"article":"... ...",
"type":"2"
}
- 响应数据:
- 成功
{
"code":"200",
"message":"success",
"data":{}
}
三、代码实现
-
controller
/** * 修改头条数据 */ @PostMapping("update") public Result updateHeadlineData(@RequestBody Headline headline){ Result result = headlineService.updateHeadlineData(headline); return result; } -
service
/** * 修改头条数据 * 1.查询version版本 * 2.补全属性,修改时间 , 版本! */ @Override public Result updateHeadlineData(Headline headline) { // 1.查询version版本 Integer version = headlineMapper.selectById(headline.getHid()).getVersion(); // 2.补全属性,修改时 ,版本! headline.setVersion(version); headline.setUpdateTime(new Date()); // 3.结果封装 headlineMapper.updateById(headline); return Result.ok(null); }
4.3.5 删除头条功能
一、需求描述
- 将要删除的新闻id发送给服务端
- 服务端校验登录是否过期,未过期则直接删除,过期则响应登录过期信息
二、接口描述
-
url地址:
headline/removeByHid -
请求方式:
post -
请求参数:
hid=1 param形成参数
-
响应数据:
-
成功
{
"code":"200",
"message":"success",
"data":{}
}
三、代码实现
直接在controller层实现即可
/**
* 删除头条数据
*/
@PostMapping("removeByHid")
public Result removeByHid(Integer hid){
headlineService.removeById(hid);
return Result.ok(null);
}

浙公网安备 33010602011771号