#前后端分离# 头条发布系统

5 篇文章 0 订阅
订阅专栏
本文介绍了前后端分离的头条发布系统开发过程,涵盖数据库设计、用户模块(包括JWT认证)和首页、头条模块的功能实现,包括新闻分页、搜索、详情查看、用户登录注册、新闻发布与修改、删除等功能。
摘要由CSDN通过智能技术生成

头条业务简介

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

预览界面

在这里插入图片描述

开源上线

https://gitcode.net/NVG_Haru/NodeJS_5161447

数据库设计

在这里插入图片描述

数据库脚本

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;


-- ----------------------------
-- 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;



SET FOREIGN_KEY_CHECKS = 1;

用户模块开发

jwt和token介绍

令牌(Token):在计算机领域,令牌是一种代表某种访问权限或身份认证信息的令牌。它可以是一串随机生成的字符或数字,用于验证用户的身份或授权用户对特定资源的访问。普通的令牌可能以各种形式出现,如访问令牌、身份令牌、刷新令牌等

在这里插入图片描述

简单理解 : 每个用户生成的唯一字符串标识,可以进行用户识别和校验

优势: token验证标识无法直接识别用户的信息,盗取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技术工具类

package com.atguigu.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字符串获取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;
        }
    }
}

使用和测试

@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);
    }

}
登录功能实现

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

接口描述

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;

    /**
     * 登录需求
     * 地址: /user/login
     * 方式: post
     * 参数:
     *    {
     *     "username":"zhangsan", //用户名
     *     "userPwd":"123456"     //明文密码
     *    }
     * 返回:
     *   {
     *    "code":"200",         // 成功状态码
     *    "message":"success"   // 成功状态描述
     *    "data":{
     *         "token":"... ..." // 用户id的token
     *     }
     *  }
     *
     * 大概流程:
     *    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);
        System.out.println("result = " + result);
        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获取用户数据

需求描述

客户端发送请求,提交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

/**
 * 地址: 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":{}
}

代码实现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);
}
用户注册功能

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

接口描述

url地址:user/regist
请求方式:POST

请求参数:

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

成功回复

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

失败回复

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

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);
}

首页模块开发

查询首页分类

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

接口描述

    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);
    }
}
分页查询首页头条信息

需求描述

  • 客户端向服务端发送查询关键字,新闻类别,页码数,页大小
  • 服务端根据条件搜索分页信息,返回含页码数,页大小,总页数,总记录数,当前页数据等信息,并根据时间降序,浏览量降序排序

接口描述

    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. 准备条件实体类
@Data
public class PortalVo {
    
    private String keyWords;
    private Integer type;
    private Integer pageNum = 1;
    private Integer pageSize =10;
}

controller

/**
 * 首页分页查询
 * @return
 */
@PostMapping("findNewPage")
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);
}

mapperxml:

<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>
查询头条详情

需求描述

  • 用户点击"查看全文"时,向服务端发送新闻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

/**
 * 详情数据查询
 * "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);

mapperxml:

<!--    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请求头后,校验用户登录是否过期并做响应
  • 前端根据响应信息提示用户进入登录页还是进入正常业务页面
  1. 接口描述

url地址:user/checkLogin
请求方式:get
请求参数: 无
请求头: token: 用户token

未过期:

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

过期:

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

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);
}

拦截器 【所有/headline开头都需要检查登陆】

@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;
        }
    }
}

拦截器配置

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private LoginProtectInterceptor loginProtectInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginProtectInterceptor).addPathPatterns("/headline/**");
    }
}
头条发布实现
  1. 需求描述
  • 用户在客户端输入发布的新闻信息完毕后
  • 发布前先请求后端的登录校验接口验证登录
  • 登录通过则提交新闻信息
  • 后端将新闻信息存入数据库
  1. 接口描述

     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){

    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);
}
3.3 修改头条回显
  1. 需求描述
  • 前端先调用登录校验接口,校验登录是否过期
  • 登录校验通过后 ,则根据新闻id查询新闻的完整信息并响应给前端
  1. 接口描述

     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

/**
 * 根据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);
}
3.4 头条修改实现
  1. 需求描述
  • 客户端将新闻信息修改后,提交前先请求登录校验接口校验登录状态
  • 登录校验通过则提交修改后的新闻信息,后端接收并更新进入数据库
  1. 接口描述

     url地址:headline/update
     请求方式:post
     请求参数:
    
    {
        "hid":"1",
        "title":"尚硅谷宣布 ... ...",
        "article":"... ...",
        "type":"2"
    }
    

成功

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

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);
}
删除头条功能
  • 将要删除的新闻id发送给服务端
  • 服务端校验登录是否过期,未过期则直接删除,过期则响应登录过期信息

接口描述
url地址:headline/removeByHid
请求方式:post
请求参数:hid=1 param形成参数

成功

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

controller

@PostMapping("removeByHid")
public Result removeById(Integer hid){
    headlineService.removeById(hid);
    return Result.ok(null);
}
SpringBoot项目实战演练(一)
JohnsonKK1的博客
03-18 537
SpringBoot项目实战演练(一) 1. 邮件定时发送 出于学习目的,使用SpringBoot框架实现一些小功能,参考了很多文章,将一些常用的知识进行融合 项目架构 首先是整体目录结构,只贴出邮件发送功能涉及的代码模块,如下图所示,该功能只涉及到展开的包: 2. 项目搭建 首先创建SpringBoot项目,本文采用的是idea,如图所示: 选择Spring Initializr,点击下一步 选择maven,java版本一般用1.8就可以,路径可以自己设置 这里可以选择一些自己需要的依赖包,创建完项目之
Spring Boot实战
11-09 4813
Spring在java EE开发中是实际意义上的标准,但我们在开发Spring的时候可能会遇到以下令人头疼的问题:1.大量配置文件的定义。 2.与第三方软件整合的技术问题。Spring每个版本的退出都以减少配置作为自己的主要目标,例如:1.推出@Component,@Service,@Repository,@Controller注解在类上声明Bean 2.推出@Configuration,@Be
Java+SpringBoot2+Mybatis+Mysql + uniapp 实现的垃圾分类前后端分离管理系统源码
06-23
Java+SpringBoot2+Mybatis+Mysql + uniapp 实现的垃圾分类前后端分离管理系统源码,可发布到iOS、Android、H5、以及各种小程序(微信/支付宝/百度/头条/QQ/钉钉)等多个平台,有完整前后台源码,系统经多次测试,运行无误,大家放心下载
SpringBoot项目实战篇,项目实战
weixin_53669566的博客
03-22 1793
【代码】SpringBoot项目实战篇。
Springboot项目实战
weixin_47188125的博客
09-12 1949
新学到的知识点 (1)获取数据 根据Postman获得网页json数据,存放在IDEA中,通过Ctrl+Alt+r格式化 在此之前需要把开头的{"ret":0,"data": 以及最后的”删除,然后\" 批量换成\” 快捷键Ctrl+r因 为我们要得到的是字符串 (2)解析数据 JSON=JavaScript Object Notation (JavaScript的对象表示法) json----》自...
SpringBoot项目实战(一)
weixin_46036165的博客
08-07 4171
SpringBoot实战 1.系统介绍 该实战项目,是一个B2C模式的职业技能在线教育系统,分为前台用户系统和后台运营平台。前台用户系统包括课程、问答、文章三大部分。后台运营平台包括会员管理、讲师管理、课程管理、文章资讯、统计分析等系统功能。 1.系统技术架构。 前后端分离 后端的主要技术架构是:SpringBoot + SpringCloud + MyBatis-Plus + MySQL+Swagger2 前端的架构是:Node.js + Vue.js + Nuxt 使用了阿里云OSS、阿里云视频点播、
springboot项目开发实战
Bejpse的博客
07-31 3192
1、我的理解是,它就是tomcat服务器启动时候,会首先加载的容器文件,(在springboot项目中,就是这个项目启动后,带动了tomcat服务器启动,然后首先会加载的容器文件,)然后这个容器中各个相关的框架类会根据框架依赖逻辑相继加载,各个类的参数也会在设置后相继加载;这种类参数设置的方式;备注4这个注解方法采用与数据库交流后获取的数据,其映射关系是直接体现在sql语句上的,如selectsys_nameasname,就是查询到的数据库的sys_name字段,是映射到实体类name上的;...
spring boot 实战,springboot实战项目,Java
09-11
4? 第1章会对Spring Boot进行概述,内容涵盖最基本的自动配置、起步依赖、命令行界面和Actuator。? 第2章会进一步深入Spring Boot,重点介绍自动配置和起步依赖。在这一章里,你将用很少的显式配置来构建一个完整的Spring应用程序。? 第3章是对第2章的补充,演示了如何通过设置应用程序属性来改变自动配置,或者在自动配置无法满足需要时彻底覆盖它。? 在第4章里我们会看到如何为Spring Boot应用程序编写自动化集成测试。? 在第5章里你将看到一种有别于传统Java开发方式的做法,Spring Boot CLI能让你通过命令行来运行应用程序,这个应用程序完全是由Groovy脚本构成的。? 讲到Groovy,第6章会介绍Grails 3,这是Grails框架的最新版本,它基于Spring Boot。? 在第7章里你将看到如何通过Spring Boot的Actuator了解运行中的应用程序,以及它是如何工作的。你还会看到如何使用Actuator的Web端点、远程shell和JMX MBean对应用程序一窥究竟。? 第8章讨论了各种部署Spring Boot应用程序的方法,包括传统的应用程序服务器部署和云部署。
java+vue前后端分离宠物商城带UNIAPP源码uniapp/Springboot/Vue/mysql
04-07
客户端使用的uniapp编写的(可发布IOS/Android/H5/微信小程序/支付宝小程序/百度小程序/头条小程序/QQ小程序) unimall-admin管理后台 unimall-admin-api 后台api unimall-app 双端app unimall-app-api unimall-biz ...
前后端分离的小演员招募小程序开发
前后端分离可以提高开发效率,提升系统的可维护性和可扩展性。 6. 数据库和SQL:SQL(Structured Query Language)是用于管理关系型数据库的标准编程语言。在项目中,数据库用于存储应用程序的数据,而SQL脚本则...
SpringBoot实战
11-21
可以前往我的github获取下载地址https://github.com/Ma5k/SpringStudy_1_3/blob/master/README.md
deflower-该项目是前后端分离的卖花网站
Honmaple的博客
08-26 617
deflower 介绍 该项目是前后端分离的卖花网站,有一个后端工程deflower,使用iade进行开发,基于jdk1.8,有两个前端工程,mall4uni,vue-admin-master,此项目采用单体架构,以使用最基础的技术来实现一个购物流程,使想初步接触电商的小伙伴能过快速理解这种类型的产品思想,实现流程,功能模块有,发布商品,花语解说,教学视频,管理员管理,前台用户管理,客服功能(简单的聊天),分类管理,个人中心等 软件架构 后端架构 技术栈 技术 说明 官网 spring
springboot应用实战
weixin_44383484的博客
02-21 518
springboot应用实战
springboot项目实战
GnodiYnehS的博客
06-29 293
https://github.com/javastacks/spring-boot-best-practice
Springboot项目-实战1-基础
最新发布
LXMXHJ的博客
02-28 511
从实战项目中学习Springboot知识。
Spring Boot实战 目录
AI天才研究院
04-01 8811
书名: Spring Boot实战 定价: 59.00元 出版社名称: 人民邮电出版社 出版日期:2016年8月 作者: Craig Walls ISBN编号: 9787115433145 内容简介 本书以Spring应用程序开发为中心,全面讲解如何运用Spring Boot提高效率,使应用程序的开发和管理更加轻松有趣。作者行文亲切流畅,以大量示例讲解了S...
Spring Boot 实战
Firstlucky77的博客
05-20 556
前言 本文以Spring 应用程序开发为中心,全面讲解如何运用Spring Boot 提高效率,使应用程序的开发和管理更加轻松有趣。作者行文亲切流畅,以大量示例讲解了Spring Boot 在各类情境中的应用,内容涵盖起步依赖、Spring Boot CLI、Groovy、Grails、Actuator。对于Spring Boot 开发应用中较为繁琐的内容,附录奉上整理完毕的表格,一目了然,方便读者查阅。 本书适合全体Java 开发人员。(懒得打字直接上图了) 目录 入门
《springboot实战》
weixin_34138377的博客
09-20 429
前言 大致翻了一下《springboot实战》这一本书,相比之前的文章,总体来说,没有什么干货,实战感觉也谈不上。仅当一本普通的科普读物,记录一下学习笔记。看完可以了解一些基本的知识,大致如下: springboot的特性有哪些? 为什么在配置文件里面配置一些变量,springboot就能提供某些功能? springboot内部自动配置原理是什么?内部如何实现? 常见的一些自定义配置有哪些? s...
写文章

分类专栏

  • Spring 项目实战 甄选 付费 5篇
  • 华为OD机试2024题库c++ 2篇
  • 鸿蒙 HarmonyOS 精粹 6篇
  • Linux 系统吐血总结 3篇
  • flask 1篇

最新评论

  • CS61C Lab 攻略:从入门到升天

    jim_parkle: 为什么只写了一个lab表情包

  • SpringBoot + Vue 抖音全平台项目

    一席嗔梦: 有教学视频么,可以出吗

  • SpringBoot + Vue 抖音全平台项目

    cellllec: 有人运行过了吗?为什么视频发布后播不了,报错空指针异常

  • 借助AI,直接刷真题也能通关软考

    2301_82243232: 好文,细节很到位!【我也写了一些相关领域的文章,希望能够得到博主的指导,共同进步!】

  • 解决 Access denied for user `root`@`localhost`

    普通网友: 干货满满!我也写了一篇获取【大厂面试真题解析、核心开发学习笔记、最新全套讲解视频、实战项目源码讲义、学习路线简历模板】的文章

大家在看

  • 对二进制减法的理解 104
  • 吱秘AI体验:一站式AI办公助手,轻松解决创作难题 409
  • Docker 容器和镜像之间有什么关系? 494
  • okHttp的使用流程-分发器与拦截器,分发器原理,分发器线程池okHttp拦截器责任链设计模式retrofit class实现思想和设计原理 43
  • 每日一题:Leetcode-85 最大矩形 253

最新文章

  • GPT4o 大战高考数学, 惨败
  • 倒排文件的设计与实现
  • Devin AI程序员是如何设计出来的
2024年19篇
2023年28篇
2022年5篇

目录

目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

热爱技术的小胡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或 充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值

深圳坪山网站建设公司赫章seo网站优化价格江苏服务专业的企业网站优化亳州网站推广优化哪家服务好如何优化网站文件命名怎样才能把网站优化上去宝鸡网站优化排名多少钱乐山企业网站优化服务邯郸家装行业网站优化推广价格专业网站seo优化公司小网站如何优化互联网网站优化广告外贸网站优化机器人龙岗网站优化服务如何网站优化失败百度快照网站优化多少钱网站图片优化的概念莱山营销型网站优化公司崂山手机网站优化如何优化网站标题网站优化的目的和方法优化网站牜就属金手指出词快包头市网站seo优化排名对网站的优化现状分析华富广告网站优化闵行区谷歌网站优化价格大兴网站整体优化网站优化到底难在哪里建材行业网站优化营销室内设计平面优化网站萧山网站优化营商香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声卫健委通报少年有偿捐血浆16次猝死汪小菲曝离婚始末何赛飞追着代拍打雅江山火三名扑火人员牺牲系谣言男子被猫抓伤后确诊“猫抓病”周杰伦一审败诉网易中国拥有亿元资产的家庭达13.3万户315晚会后胖东来又人满为患了高校汽车撞人致3死16伤 司机系学生张家界的山上“长”满了韩国人?张立群任西安交通大学校长手机成瘾是影响睡眠质量重要因素网友洛杉矶偶遇贾玲“重生之我在北大当嫡校长”单亲妈妈陷入热恋 14岁儿子报警倪萍分享减重40斤方法杨倩无缘巴黎奥运考生莫言也上北大硕士复试名单了许家印被限制高消费奥巴马现身唐宁街 黑色着装引猜测专访95后高颜值猪保姆男孩8年未见母亲被告知被遗忘七年后宇文玥被薅头发捞上岸郑州一火锅店爆改成麻辣烫店西双版纳热带植物园回应蜉蝣大爆发沉迷短剧的人就像掉进了杀猪盘当地回应沈阳致3死车祸车主疑毒驾开除党籍5年后 原水城县长再被查凯特王妃现身!外出购物视频曝光初中生遭15人围殴自卫刺伤3人判无罪事业单位女子向同事水杯投不明物质男子被流浪猫绊倒 投喂者赔24万外国人感慨凌晨的中国很安全路边卖淀粉肠阿姨主动出示声明书胖东来员工每周单休无小长假王树国卸任西安交大校长 师生送别小米汽车超级工厂正式揭幕黑马情侣提车了妈妈回应孩子在校撞护栏坠楼校方回应护栏损坏小学生课间坠楼房客欠租失踪 房东直发愁专家建议不必谈骨泥色变老人退休金被冒领16年 金额超20万西藏招商引资投资者子女可当地高考特朗普无法缴纳4.54亿美元罚金浙江一高校内汽车冲撞行人 多人受伤

深圳坪山网站建设公司 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化