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

news2025/1/16 16:55:18

头条业务简介

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

预览界面
在这里插入图片描述

开源上线

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

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

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

相关文章

Abstract Factory抽象工厂模式(对象创建)

抽象工厂模式&#xff1a;Abstract Factory 链接&#xff1a;抽象工厂模式实例代码 解析 目的 在软件系统中&#xff0c;经常面临着“一系列相互依赖的对象工作”&#xff1b;同时&#xff0c;由于需求的变化&#xff0c;往往存在更多系列对象的创建工作。 如何应对这种变化…

基于Javaee的影视创作论坛的设计与实现+vue论文

摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装影视创作论坛软件来发挥其高效地信息处理的作用&#xff0c…

Typora快捷键设置详细教程

文章目录 一、快捷键设置步骤二、设置快捷键简单案例参考资料 一、快捷键设置步骤 在typora软件中&#xff0c;快捷键的设置步骤主要为&#xff1a; 打开【文件】–>【偏好设置】&#xff0c;找到【通用】–>【打开高级设置】&#xff0c;找到 conf.user.json 文件。 然…

c++哈希表——超实用的数据结构

文章目录 1. 概念引入1.1 整数哈希1.1.1 直接取余法。1.1.2 哈希冲突1.1.2.1 开放寻址法1.1.2.2 拉链法 1.2 字符串哈希 3.结语 1. 概念引入 哈希表是一种高效的数据结构 。 H a s h Hash Hash表又称为散列表&#xff0c;一般由 H a s h Hash Hash函数(散列函数)与链表结构共同…

安全生产知识竞赛活动方案

为进一步普及安全生产法律法规知识&#xff0c;增强安全意识&#xff0c;提高安全技能&#xff0c;经研究&#xff0c;决定举办以“加强安全法治、保障安全生产”为主题的新修订《安全生产法》知识竞赛活动&#xff0c;现将有关事项通知如下&#xff1a; 一、活动时间&#xf…

【网络安全】网络隔离设备

一、网络和终端隔离产品 网络和终端隔离产品分为终端隔离产品和网络隔离产品两大类。终端隔离产品一般指隔离卡或者隔离计算机。网络隔离产品根据产品形态和功能上的不同&#xff0c;该类产品可以分为协议转换产品、网闸和网络单向导入产品三种。 图1为终端隔离产品的一个典型…

HTML5+CSS3②——图像、超链接、音频、视频

目录 图像 超链接 音频 视频 图像 作用&#xff1a;在网页中插入图片 单标签&#xff1a; 标签名&#xff1a;<img src"图片的URL"> <img src"图片的URL" alt"替换文本" title"提示文本"> 属性写在尖括号里面&#xff0c;…

WPF+Halcon 培训项目实战(12):WPF导出匹配模板

文章目录 前言相关链接项目专栏运行环境匹配图片WPF导出匹配模板如何了解Halcon和C#代码的对应关系逻辑分析&#xff1a;添加截取ROI功能基类矩形圆形 生成导出模板运行结果&#xff1a;可能的报错你的文件路径不存在你选择的区域的内容有效信息过少 前言 为了更好地去学习WPF…

YOLOv8改进 添加可变形注意力机制DAttention

一、Deformable Attention Transformer论文 论文地址&#xff1a;arxiv.org/pdf/2201.00520.pdf 二、Deformable Attention Transformer注意力结构 Deformable Attention Transformer包含可变形注意力机制&#xff0c;允许模型根据输入的内容动态调整注意力权重。在传统的Tra…

模型 安索夫矩阵

本系列文章 主要是 分享模型&#xff0c;涉及各个领域&#xff0c;重在提升认知。产品市场战略。 1 安索夫矩阵的应用 1.1 江小白的多样化经营策略 使用安索夫矩阵来分析江小白市场战略。具体如下&#xff1a; 根据安索夫矩阵&#xff0c;江小白的现有产品是其白酒产品&…

西北工业大学计算机组成原理实验报告——verilog前两次

说明 为了有较好的可读性&#xff0c;报告仅仅粘贴关键代码。该PDF带有大纲功能&#xff0c;点击大纲中的对应标题&#xff0c;可以快速跳转。 实验目标 掌握单周期CPU执行指令的流程和原理&#xff1b;学习使用verilog HDL语言实现单周期CPU, 并通过功能仿真&#xff1b;提…

leetcode 315. 计算右侧小于当前元素的个数(hard)【小林优质解法】

链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 代码&#xff1a; class Solution {int[]counts; //用来存储结果int[]index; //用来绑定数据和原下标int[]helpNums; //用于辅助排序 nums 数组int[]helpIndex; //用于辅助排序 i…

Javaweb之Mybatis入门的详细解析

Mybatis入门 前言 在前面我们学习MySQL数据库时&#xff0c;都是利用图形化客户端工具(如&#xff1a;idea、datagrip)&#xff0c;来操作数据库的。 在客户端工具中&#xff0c;编写增删改查的SQL语句&#xff0c;发给MySQL数据库管理系统&#xff0c;由数据库管理系统执行S…

分享好用稳定快递查询api接口(对接简单)

提供实时查询和自动识别单号信息。稳定高效&#xff0c;调用简单方便&#xff0c;性价比高&#xff0c;一条链接即可对接成功。 使用数据平台该API接口需要先注册后申请此API接口。申请成功后赠送免费次数&#xff0c;可直接在线请求接口数据。 接口地址&#xff1a;https://…

迭代归并:归并排序非递归实现解析

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《数据结构&算法》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! &#x1f4cb; 前言 归并排序的思想上我们已经全部介绍完了&#xff0c;但是同时也面临和快速排序一样的问题那就是递…

『亚马逊云科技产品测评』活动征文|云服务器如何快速搭建个人博客(图文详解)

授权声明&#xff1a;本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 Developer Centre, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科技官方渠道 文章目录 引言一、前期准备步骤1.1 准备一个亚马逊 EC2 服务器1.2 进入控…

Stimulsoft BI Designer 2024.1.2 Crack

Stimulsoft BI Designer Do you want to create reports and dashboards, but you do not need to create your application? We want to offer you a great solution – Stimulsoft Designer! All you need is to connect your data, drag it onto the template page, config…

uniapp打包Android、Ios、微信小程序

首先我们需要在我们的代码中&#xff0c;把我们所要用到的配置信息配置好&#xff0c;在检查一下我们测试的内容是否有打开&#xff08;取消注释&#xff09;&#xff0c;在检查一下我们的版本信息是否正确&#xff0c;查看一下接口ip是否是正式线 这里的配置信息一定要配置好…

二进制、位运算和掩码运算,小白鼠测试示例

1. 二进制 二进制是一种基于两个数字0和1的数制系统。它可以表示两种状态&#xff0c;即开和关。所有输入电脑的任何信息最终都要转化为二进制。目前通用的是ASCII码。最基本的单位为bit。 在计算机科学中&#xff0c;二进制是最常用的数制系统&#xff0c;因为计算机内部的所…

【MySQL】主从异步复制配置

您好&#xff0c;我是码农飞哥&#xff08;wei158556&#xff09;&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f4aa;&#x1f3fb; 1. Python基础专栏&#xff0c;基础知识一网打尽&#xff0c;9.9元买不了吃亏&#xff0c;买不了上当。 Python从入门到精…