火影 发表于 2024-9-5 20:57:26

微头条(全栈进阶项目,主要偏向于后端)

首先讲述一下该项目能实现的功能,通过这些功能开始编写后端代码。
https://i-blog.csdnimg.cn/direct/71e66a9a72af41dba48e09eaab25a9e8.png
所运用到的前后端技术栈:
https://i-blog.csdnimg.cn/direct/2066483394374370b02d1a27acee2742.pnghttps://i-blog.csdnimg.cn/direct/04e8f96641e0497595fac36b4a641412.png
本次项目开发依旧利用到了mvc的设计模式结构,具体的解释可以见我上一篇博客。
https://i-blog.csdnimg.cn/direct/56640e0b83f545db8cb017e0da6edf21.png
如图该项目依旧是和上一篇日程管理系统的结构大相径庭。common层是规定后端给前端相应的状态码,dao层用来和后端数据库发生交互,filters层放入了校验相关的过滤器,pojo层是创建相关实体类,service层是处理除了和数据库交互之外的其他业务需求(例如将明文暗码转换为密文暗码,以及背面要讲到的处理消息分页查询功能),util层放入相关的工具类。
controller层我们这次也称为业务处理接口,作用就是连接前后端,前端发送需求给后端,controller层举行吸收然后发送给service层继承后端的操作。这次项目会接纳接口文档的方式来对后端代码举行开发,对照接口文档对controller层代码举行开发。
同时在截图中我也是给出了相关的jar包,在maven中都可以找到资源并下载。
Vo层解析

这次在开发pojo层的时候会发现多了一层叫Vo层
https://i-blog.csdnimg.cn/direct/d4e1998362bc4f688edf376c10ffae45.png
这层Vo用于办理多表查询时,好几个表要用到一个实体类来装,这时pojo层单独的实体类就装不了这些数据了。通俗来说就是一个实体类的属性少于SQL 查询结果的字段,就装不下全部的数据,以是要再定义一个类。背面会利用到多表查询就是要利用Vo层中的类,将多张表中的属性封装到一个实体类中。
Pojo层代码

 NewsUser :
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;

@AllArgsConstructor
@NoArgsConstructor
@Data
public class NewsUser implements Serializable {
    private Integer uid;
    private String username;
    private String userPwd;
    private String nickName;
} NewsType:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;

@AllArgsConstructor
@NoArgsConstructor
@Data
public class NewsType implements Serializable {
    private Integer tid;
    private String tname;
} NewsHeadline:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;

@AllArgsConstructor
@NoArgsConstructor
@Data
public class NewsHeadline implements Serializable {
    private Integer hid;
    private String title;
    private String article;
    private Integer type;
    private Integer publisher;
    private Integer pageViews;
    private Date createTime;
    private Date updateTime;
    private Integer isDeleted;

} HeadlineDetailVo:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;

@AllArgsConstructor
@NoArgsConstructor
@Data
public class HeadlineDetailVo implements Serializable {
    private Integer hid;
    private String title;
    private String article;
    private Integer type;
    private String typeName;
    private Integer pageViews;
    private Long pastHours;
    private Integer publisher;
    private String author;
} HeadlinePageVo:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;

@AllArgsConstructor
@NoArgsConstructor
@Data
public class HeadlinePageVo implements Serializable {
    private Integer hid;
    private String title;
    private Integer type;
    private Integer pageViews;
    private Long pastHours;
    private Integer publisher;
} HeadlineQueryVo:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;

@AllArgsConstructor
@NoArgsConstructor
@Data
public class HeadlineQueryVo implements Serializable {
    private String keyWords;
    private Integer type ;
    private Integer pageNum;
    private Integer pageSize;
} Postman工具

PostMan是一个接口测试工具,在做接口测试的时候,Postman相当于一个客户端,它可以模拟用户发起的各类http请求,将请求数据发送至服务端,获取对应的相应结果,从而验证相应中的结果数据是否和预期值相匹配。
https://i-blog.csdnimg.cn/direct/392eab63709f46e098d8a588ebf0df48.png
 右边的POST那一行是输入要往后端的哪一个方法发送的url,即发送到Controller层。旁边的POST也可以修改成GET,即指定发送方式。下面的Key-Value就是指定要发送的键值对格式。
以第一个需求(将消息的5种范例表现在页面最上方)举例

https://i-blog.csdnimg.cn/direct/69a461094c6647a39604a3f83b8785d4.png
首先我们需要在controller层中多创建一个PortalController层作为门户控制器,那些不需要登录,不需要做增删改的门户页的请求都放在这里。然后根据前端需要的信息:
https://i-blog.csdnimg.cn/direct/4da471a1c31947c9a5fb24545075045d.png
 可知所需要的实体类是pojo中的NewsType类。
1,创建方法用于调用service层:
    /**
   * 查询所有头条类型的业务接口实现
   * @param req
   * @param resp
   * @throws ServletException
   * @throws IOException
   */
    protected void findAllTypes(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      //查询所有的新闻类型,装入Result响应给客户端
      List<NewsType> newsTypeList = typeService.findAll();
      WebUtil.writeJson(resp, Result.ok(newsTypeList));
    } 2,在NewsTypeService接口中实现:
import com.ryy.headline.pojo.NewsType;
import java.util.List;

public interface NewsTypeService {
    /**
   * 查询所有头条类型的方法
   * @return 多个头条类型以List<NewsType>集合形式返回
   */
    List<NewsType> findAll();
} 3,调用dao层代码:
import com.ryy.headline.dao.NewsTypeDao;
import com.ryy.headline.dao.Impl.NewsTypeDaoImpl;
import com.ryy.headline.pojo.NewsType;
import com.ryy.headline.service.NewsTypeService;

import java.util.List;

public class NewsTypeServiceImpl implements NewsTypeService {
    private NewsTypeDao typeDao =new NewsTypeDaoImpl();
    @Override
    public List<NewsType> findAll() {
      return typeDao.findAll();
    }
} 4,发送sql语句:
import com.ryy.headline.dao.BaseDao;
import com.ryy.headline.dao.NewsTypeDao;
import com.ryy.headline.pojo.NewsType;

import java.util.List;

public class NewsTypeDaoImpl extends BaseDao implements NewsTypeDao {
    @Override
    public List<NewsType> findAll() {
      String sql = "select tid,tname from news_type";
      return baseQuery(NewsType.class,sql);
    }
} 当这些代码编写好之后我们就能利用postman举行测试:
https://i-blog.csdnimg.cn/direct/de8980a699884a7fa1e2a92bf15c36e2.png
我们输入相应的url举行查询,postman就会表现后端发送过来的数据,而返回的数据与刚才前端需要的样子类似,即代表代码无题目。
登录需求的实现

token

在实现登录功能之前需要讲述一个知识点叫token,其作用其实和我们之前利用的session域的作用是一样的,都是将数据暂时存储起来然后下次登录时候就去这些域中探求检察是否已经登岸过。
而利用token的优势就在于后端不用产生大量session对象,前端也不用存储一大堆cookie,token会存储在客户端的localStorage中,每次发送请求时,都将token以请求头的情势发送给服务器。不用session域的原因是当session特殊多的时候就牵扯到高并发题目,对于Io流的读取就会造成巨大的性能损失。如图:
https://i-blog.csdnimg.cn/direct/b683a3ac03514f779e37274e6f90cfac.png
我们可以发现token是存储在客户端中的,而如许做可以大大减小后端服务器的压力。
以登录功能举例:(第一步)
https://i-blog.csdnimg.cn/direct/e7c3908e5b604a4c983b44a96ea1f705.png这里要注意登录乐成之后还会要将token口令给到客户端,而客户端下次发送任何请求时只需要判断本地token是否为空,而无需再次触发登录验证。可以或许实现的业务逻辑就是用户可以或许在规定时间内免登录直接访问消息首页。
(第二步)
https://i-blog.csdnimg.cn/direct/8898e0dbb4c84816a127015de5c03865.png
在登录乐成之后页面右上角的“注册”“登录”将会变为“欢迎...登录”。后端通过解析token将id取出然后到数据库中找到对应数据,前端就会将返回的nickName存储到pinia中,然后通过前端页面绑定pinia即可将nickName表现到页面上。
token的相关工具类:

由于token是在后端生成的:(一共有三个方法,第一是生成token密文字符串,第二是解析token密文,第三是判断token的时效性是否到期),该工具类放在utils包下
import com.alibaba.druid.util.StringUtils;
import io.jsonwebtoken.*;

import java.util.Date;

public class JwtHelper {
    private static long tokenExpiration = 1000*60*60;//定义token的过期时间
    private static String tokenSignKey = "123456"; //需要知道这一段数据才能解析出token密文

    //生成token字符串
    public static String createToken(Long userId ) {
      String token = Jwts.builder()

                .setSubject("YYGH-USER")
                .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
                .claim("userId", userId) //返回的密文信息有userId

                .signWith(SignatureAlgorithm.HS512, tokenSignKey)
                .compressWith(CompressionCodecs.GZIP)
                .compact();
      return token;
    }

    //从token字符串获取userid,即从密文形式解析出userId
    public static 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 static 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;
      }
    }
} token的相关前端代码:

1,token-utils.ts中先得到token:
const TokenKey = 'vue_admin_template_token'

export function getToken() {
return localStorage.getItem(TokenKey)
}

export function setToken(token: string) {
localStorage.setItem(TokenKey, token)
}

export function removeToken() {
localStorage.removeItem(TokenKey)
} 2,userInfo.js中的pinia共享数据来得到token:
import { getToken, removeToken, setToken } from '../utils/token-utils';

export const useUserInfoStore = defineStore('userInfo', {

        state: () => ({
    token: getToken(),
    nickName: '',
    uid: '',
}), 3,request.js来判断token是否为空,即添加一个请求拦截器:
// 添加请求拦截器
service.interceptors.request.use((config) => {
NProgress.start()//开启进度条
// 如果有token, 通过请求头携带给后台
const userInfoStore = useUserInfoStore(pinia) // 如果不是在组件中调用,必须传入pinia
const token = userInfoStore.token
   if (token) {
      // config.headers['token'] = token// 报错: headers对象并没有声明有token, 不能随便添加
      (config.headers)['token'] = token
    }
return config;
}); 4,请求乐成后, 取出token保存  pinia和local中
        import { getLogin,getUserInfo } from '../api/index';

    actions: {
    // 登陆的异步action
    async login (loginForm) {
       // 发送登陆的请求
      const result = await getLogin(loginForm)
      // 请求成功后, 取出token保存pinia和local中
      const token = result.token
      
      this.token = token
      setToken(token)
    }, 5,以post情势向后端发送请求(api/index)
//登录的接口
export const getLogin = (info) => {
return request.post("user/login",info);
}; 正式实现登录需求

登录功能看图可知后端接口需要有吸收两个请求,一个是校验暗码和用户名是否精确,一个是解析客户端发来的token。
https://i-blog.csdnimg.cn/direct/adb80f893f4a4bae9645602706125206.pnghttps://i-blog.csdnimg.cn/direct/29ea987031754f9884841317a56a3ec1.png
 
处理登岸表单提交的业务接口实现

根据文档提供的路径可知在NewsUserController类中需要有一个login的方法用来吸收POST请求情势发来的数据并且将数据发送给service层再到数据库中举行一系列操作。同时还需要验证如果数据库中确实存在该用户,那就需要给客户端创建一个token口令并返回表示登录乐成。
同时还需要注意格式题目,在文档中可以看到token情势是:"token":"... ...",说明返回的token需要是键值对的情势,因此需要创建一个Map来封装。
代码实现:
    /**
   * 处理登陆表单提交的业务接口实现
   *
   * @param req
   * @param resp
   * @throws ServletException
   * @throws IOException
   */
    protected void login(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      //接收用户名和密码
      /*{
            "username":"zahngsan", //用户名
            "userPwd":"123456", //明文密码
         */
      NewsUser paramUser = WebUtil.readJson(req, NewsUser.class);

      //调用服务层方法实现登录
      NewsUser loginUser = userService.findByUsername(paramUser.getUsername());
      Result result = null;
      if (null != loginUser) {
            if (MD5Util.encrypt(paramUser.getUserPwd()).equalsIgnoreCase(loginUser.getUserPwd())) {
                //返回的信息还需要有一个token,因此在这里创建一个token密文
                Integer uid = loginUser.getUid();
                String token = JwtHelper.createToken(uid.longValue());
                //由于需要返回键值对形式的token,因此需要创建一个Map进行发送
                //"token":"... ..."
                Map data = new HashMap();
                data.put("token", token);
                result = Result.ok(data);
            } else {
                result = Result.build(null, ResultCodeEnum.PASSWORD_ERROR);
            }
      } else {
            result = Result.build(null, ResultCodeEnum.USERNAME_ERROR);
      }
      //向客户端响应登录验证信息
      WebUtil.writeJson(resp, result);
    } 然后就是按照MVC设计模式到Service层,再从Service层到DAO层书写相关sql语句对数据库举行CRUD操作。
NewsUserServiceImpl:
    @Override
    public NewsUser findByUsername(String username) {
      return userDao.findByUsername(username);
    } NewsUserDaoImpl:
    @Override
    public NewsUser findByUsername(String username) {
      String sql = """
                select
                  uid,
                  username,
                  user_pwd userPwd,
                  nick_name nickName
                from
                  news_user
                where
                  username=?
                """;
      //由于baseQueryObject是返回单个值的,而此处的需求是返回具体的对象,即使只有一个对象
      List<NewsUser> newsUserList = baseQuery(NewsUser.class, sql, username);
      return newsUserList != null && newsUserList.size() > 0 ? newsUserList.get(0) : null;
    } 根据token口令得到用户信息的接口实现

https://i-blog.csdnimg.cn/direct/72647c27da9c498ca72c84e8e5c3d060.pnghttps://i-blog.csdnimg.cn/direct/74278a50dcc04d0e8de32119d4d69000.png
这里主要是写对于请求头中的token举行的解析操作,判断完token不为空并且时效性还在之后会根据token中解析出来的id到数据库中探求对应id并且把nickName返回表现在页面右上角原本是登录和注册按钮的位置。
返回情势以键值对情势返回。
注意这边有一个细节就是根据文档要将userPwd设置为空串,不然全部人都知道暗码了,不符合业务逻辑。
NewsUserController:
    /**
   * 根据token口令获得用户信息的接口实现
   *
   * @param req
   * @param resp
   * @throws ServletException
   * @throws IOException
   */
    protected void getUserInfo(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      //获取请求中的token
      String token = req.getHeader("token");
      Result result = Result.build(null, ResultCodeEnum.NOTLOGIN);
      if (null != token && (!"".equals(token))) { //判断是否为空串
            if (!JwtHelper.isExpiration(token)) { //判断token是否过期
                Integer userId = JwtHelper.getUserId(token).intValue();
                NewsUser newsUser = userService.findByUid(userId);
                if (null != newsUser) {
                  //通过校验 查询用户信息放入Result
                  Map data = new HashMap(); //依旧需要键值对形式
                  newsUser.setUserPwd(""); //需要将密码设置为空串进行返回
                  data.put("loginUser", newsUser);
                  result = Result.ok(data);
                }
            }
      }
      WebUtil.writeJson(resp, result);
    }  然后依旧利用MVC模式到DAO层,我这边就直接演示DAO层代码了:(根据文档提供的格式将用户数据以List情势返回)
    @Override
    public NewsUser findByUid(Integer userId) {
      String sql = """
                select
                  uid,
                  username,
                  user_pwd userPwd,
                  nick_name nickName
                from
                  news_user
                where
                  uid=?
                """;
      //由于baseQueryObject是返回单个值的,而此处的需求是返回具体的对象,即使只有一个对象
      List<NewsUser> newsUserList = baseQuery(NewsUser.class, sql, userId);
      return newsUserList != null && newsUserList.size() > 0 ? newsUserList.get(0) : null;
    } 注册需求的实现

注册用户的操作和校验用户是否已经被抢注的两个接口,前端先向后端发送校验请求(见api-index.js)然后然后再向后端发送注册的请求
https://i-blog.csdnimg.cn/direct/57379b44836549aca56ad246dc3d8d39.pnghttps://i-blog.csdnimg.cn/direct/e34389debbe3441b913c835aef399ade.png
根据文档相关信息可知,首先需要去数据库校验该注册的用户是否己经在数据库中存在了,实行checkUsername方法,如果返回的结果为空则说明数据库中没有该用户,代表可以注册,既然可以注册那就实行login方法举行注册。
NewsUserController:
    /**
   * 完成注册的业务接口
   *
   * @param req
   * @param resp
   * @throws ServletException
   * @throws IOException
   */
    protected void regist(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      //接收JSON信息
      NewsUser registUser = WebUtil.readJson(req, NewsUser.class);
      //调用服务层方法
      Integer rows = userService.registUser(registUser);
      //根据存入是否成功处理响应值
      Result result = Result.ok(null);
      if (rows == 0) {
            result = Result.build(null, ResultCodeEnum.USERNAME_USED);
      }
      WebUtil.writeJson(resp, result);
    }

    /**
   * 校验用户名是否被占用的业务接口实现
   *
   * @param req
   * @param resp
   * @throws ServletException
   * @throws IOException
   */
    protected void checkUserName(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      //获取账号(以键值对形式接收)
      String username = req.getParameter("username");

      //根据用户名查询用户信息 找到了返回505 找不到就返回200(代表能够注册)
      NewsUser newsUser = userService.findByUsername(username);
      Result result = Result.ok(null);//先给result一个初始的默认值
      if (null != newsUser) {
            //如果返回的结果不是为空代表已经被抢注了
            result = Result.build(null, ResultCodeEnum.USERNAME_USED);
      }
      WebUtil.writeJson(resp, result);
    } DAO层:
    @Override
    public NewsUser findByUsername(String username) {
      String sql = """
                select
                  uid,
                  username,
                  user_pwd userPwd,
                  nick_name nickName
                from
                  news_user
                where
                  username=?
                """;
      //由于baseQueryObject是返回单个值的,而此处的需求是返回具体的对象,即使只有一个对象
      List<NewsUser> newsUserList = baseQuery(NewsUser.class, sql, username);
      return newsUserList != null && newsUserList.size() > 0 ? newsUserList.get(0) : null;
    } registUser的service层需要实行将明文暗码转为密文暗码的操作,不能写在dao层中,
NewsUserServiceImpl:
    @Override
    public Integer registUser(NewsUser registUser) {
      //处理增加数据的业务
      //用户传来的信息需要将明文密码转换成密文密码
      registUser.setUserPwd(MD5Util.encrypt(registUser.getUserPwd()));
      return userDao.insertUser(registUser);
    } DAO层实现向数据库中新增数据的操作:
    @Override
    public Integer insertUser(NewsUser registUser) {
      String sql = """
                insert into news_user values (DEFAULT,?,?,?)
                """;
      return baseUpdate(sql,
                registUser.getUsername(),
                registUser.getUserPwd(),
                registUser.getNickName()
      );
    } 分页查询

首先需要解释一下分页查询:即用户点击差别的消息范例,在页面上会表现出对应消息范例的消息。并且用户如果利用关键词搜索也能在页面上表现出对应关键字的消息。
细节:一页上可以表现多少条消息可以修改。当点击下一页时,页面数会+1。若查询出来6条消息纪录,一页上最多表现5条纪录,末了一条消息就必须再单开一页到下一页上表现。
https://i-blog.csdnimg.cn/direct/dc43c31c5cfa4de38af861fff6253b32.png
 如上图利用F12之后表现出的数据:
keyWords:关键字查询,type:查询消息的范例(体育?娱乐?... ...),
pageNum:查询出来的消息数一共占多少页,pageSize:一页上最多表现多少条消息。
如图文档所示为服务器相应回来的数据:
https://i-blog.csdnimg.cn/direct/377806b6d44f488391552457a90c60a3.pnghttps://i-blog.csdnimg.cn/direct/110613b5f05d4114a4cb6c77795b1139.png
首先需要对文档中的各个参数举行分类:
根据请求参数,此时就要利用到vo中本身创建的实体类了
此处一共举行了两次Map的嵌套:
第一层就是外层的pageInfo为key,然后pageInfo中存储的各对象为value
第二层是内部的pageInfo,此时key为pageData, pageNum, pageSize... ...,pageData数组中存放的各页面属性为value(如消息id,消息标题等),pageNum背面的页码数... ...
   //将参数转达给服务层举行分页查询
        Map pageInfo = headlineService.findPage(headlineQueryVo);
        Map data = new HashMap();
        data.put("pageInfo", pageInfo);

PortalController:
    protected void findNewsPage(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      //接收请求中的参数
      HeadlineQueryVo headlineQueryVo = WebUtil.readJson(req, HeadlineQueryVo.class);
      //将参数传递给服务层进行分页查询
      /*
      pageData:[
            {
                  "hid":"1",                     // 新闻id
                                    "title":"尚硅谷宣布 ... ...",   // 新闻标题
                                    "type":"1",                  // 新闻所属类别编号
                                    "pageViews":"40",            // 新闻浏览量
                                    "pastHours":"3",            // 发布时间已过小时数
                                    "publisher":"1"
         }
      ]
      pageNum:1,
      pageSize:1,
      totalPage:1,
      totalSize:1
         */
      Map pageInfo = headlineService.findPage(headlineQueryVo);
      Map data = new HashMap();
      data.put("pageInfo", pageInfo);
      //将分页查询的结果转换成json相应给客户端
      WebUtil.writeJson(resp, Result.ok(data));
    }
} 内层的map文档给我们提供了5个需要的参数:pageNum,pageSize,totalSize,totalSize,pageInfo。
NewsHeadlineServiceImpl:(注:办理了页数加一题目)
    @Override
    public Map findPage(HeadlineQueryVo headlineQueryVo) {
      //这两条数据都是从客户端传来的信息,可以原封不动进行返回
      int pageNum = headlineQueryVo.getPageNum();
      int pageSize = headlineQueryVo.getPageSize();
      //要将pageData数组中的对象做成一个集合,里面需要的实体类对象在Vo中已经创建完毕
      List<HeadlinePageVo> pageData = headlineDao.findPageList(headlineQueryVo);
      int totalSize = headlineDao.findPageCount(headlineQueryVo);

      //totalPage的总页码数是根据totalSize和pageNum共同决定的,总页数如果可以被整除那就直接返回页数
      //如果不能被整除那就把余下的新闻加一页显示到下一页上去
      int totalPage = totalSize % pageSize == 0 ? totalSize/pageSize : totalSize/pageSize + 1;
      //把上面五条数据放在一个大的Map中
      Map pageInfo = new HashMap();
      pageInfo.put("pageNum", pageNum);
      pageInfo.put("pageSize", pageSize);
      pageInfo.put("totalSize", totalSize);
      pageInfo.put("totalPage", totalPage);
      pageInfo.put("pageData", pageData);

      return pageInfo;
    } findPageList的DAO层代码:
    /*HeadlinePageVo中的各实体类(pageData)
      private Integer hid;
      private String title;
      private Integer type;
      private Integer pageViews;
      private Long pastHours;
      private Integer publisher;

      剩余的四条数据
      private String keyWords;
      private Integer type ;
      private Integer pageNum;
      private Integer pageSize;
   */
    @Override
    public List<HeadlinePageVo> findPageList(HeadlineQueryVo headlineQueryVo) {
      //需要准备一个集合用来装入拼接到sql语句最后的?中的参数,此处传来的是一个类对象而不是一个属性,因此需要集合进行封装
      List params = new ArrayList();
      //TIMESTAMPDIFF(HOUR,create_time,now())时间计算函数,返回当前时间减去发布时间的时间差,并且以小时为单位
      String sql = """
                select
                   hid,
                   title,
                   type,
                   page_views pageViews,
                   TIMESTAMPDIFF(HOUR,create_time,now()) pastHours,
                   publisher
                from
                   news_headline
                where
                   is_deleted = 0
                """;
      if (headlineQueryVo.getType() != 0) { //查询新闻类型,只要点击的是界面上微头条标题以外的其他类型新闻都能执行查询和页面跳转
            sql = sql.concat(" and type = ? ");
            params.add(headlineQueryVo.getType());
      }
      //搜索框中输入的不是空串,也会到数据库中去进行查询并返回关键字对应的新闻
      if (headlineQueryVo.getKeyWords() != null && !headlineQueryVo.getKeyWords().equals("")) {
            sql = sql.concat(" and title like ? "); //使用了模糊查询
            params.add("%" + headlineQueryVo.getKeyWords() + "%");
      }
      //根据发布的时间进行升序排列,如果发布时间相同再根据浏览量进行降序排列
      sql = sql.concat(" order by pastHours ASC, page_views DESC ");
      //由于是分页查询,因此还涉及到目标新闻的前面已经过去了多少条新闻,以及距离目标新闻页面还要取多少条新闻才能到目标页
      sql = sql.concat(" limit ? , ? ");
      params.add((headlineQueryVo.getPageNum() - 1) * headlineQueryVo.getPageSize());
      params.add(headlineQueryVo.getPageSize());

      return baseQuery(HeadlinePageVo.class, sql, params.toArray());
    } 需要举行消息范例与关键词搜索举行if语句的判断对sql语句举行适当的拼接然后到数据库中举行对应查找。需要注意的细节是消息是讲究一个发布时间,浏览量的排序的,因此也需要将发布的时间举行升序排列,如果发布时间类似再根据浏览量举行降序排列。
代码解析:
   分页查询题目://由于是分页查询,因此还涉及到目标消息的前面已经过去了多少条消息,以及隔断目标消息页面还要取多少条消息才能到目标页
        sql = sql.concat(" limit ? , ? ");
        params.add((headlineQueryVo.getPageNum() - 1) * headlineQueryVo.getPageSize());
        params.add(headlineQueryVo.getPageSize());
这段代码在我本身理解的时候也有一些含糊不清,我会只管以举例的方式解释清楚:
这段代码的作用是根据用户请求的页码和每页表现的纪录数,对查询结果举行分页,返回指定页码的数据。
举例:假设 headlineQueryVo.getPageNum() = 2 (消息到达第二页)且 headlineQueryVo.getPageSize() = 10(每页只能表现10条消息),那么分页查询的 limit 子句将被解释为 LIMIT 10, 10,表示从第11条纪录开始,返回接下来的10条纪录(即需要表现下一页上的消息),如果剩余纪录数不足10条那就将剩余纪录全部返回。
还需要的一个是totalSize,即一共通过搜索返回了多少条消息,只有得到了消息的总数才能对消息举行分页操作。
findPageCount:(代码与上面的分页查询返回对应消息的代码相似)
    @Override
    public int findPageCount(HeadlineQueryVo headlineQueryVo) {
      //需要准备一个集合用来装入拼接到sql语句最后的?中的参数,此处传来的是一个类对象而不是一个属性,因此需要集合进行封装
      List params = new ArrayList();
      //TIMESTAMPDIFF(HOUR,create_time,now())时间计算函数,返回当前时间减去发布时间的时间差,并且以小时为单位
      String sql = """
                select
                   count(1)
                from
                   news_headline
                where
                   is_deleted = 0
                """;
      if (headlineQueryVo.getType() != 0) { //查询新闻类型,只要点击的是界面上微头条标题以外的其他类型新闻都能执行查询和页面跳转
            sql = sql.concat(" and type = ? ");
            params.add(headlineQueryVo.getType());
      }
      //搜索框中输入的不是空串,也会到数据库中去进行查询并返回关键字对应的新闻
      if (headlineQueryVo.getKeyWords() != null && !headlineQueryVo.getKeyWords().equals("")) {
            sql = sql.concat(" and title like ? "); //使用了模糊查询
            params.add("%" + headlineQueryVo.getKeyWords() + "%");
      }

      Long count = baseQueryObject(Long.class, sql, params.toArray());
      return count.intValue();
    } 检察头条详情

不但要做到检察全文,还要做到当前浏览量+1,并且还涉及到sql的多表查询题目。
https://i-blog.csdnimg.cn/direct/e4e5b1abafbc456085f988c8c4e60024.png
 根据文档注释的参数可知要调用的是HeadlineDetailVo实体类,并且需要得到用户的hid
PortalController层:
    /**
   * 查看头条全文的接口实现
   * @param req
   * @param resp
   * @throws ServletException
   * @throws IOException
   */
    protected void showHeadlineDetail(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      //接收要查询的头条的hid
      int hid = Integer.parseInt(req.getParameter("hid"));
      //调用服务层完成查询处理
      HeadlineDetailVo headlineDetailVo = headlineService.findHeadlineDetail(hid);
      //将查询到的信息响应给客户端
      Map data = new HashMap();
      data.put("headline", headlineDetailVo);
      WebUtil.writeJson(resp, Result.ok(data));
    } 要做到当前浏览量+1和查询头条详情需要分别到dao层中去实现
NewsHeadlineServiceImpl:
    @Override
    public HeadlineDetailVo findHeadlineDetail(int hid) {
      //修改该头条的浏览量 +1
      headlineDao.incrPageViews(hid);
      //查询头条的详情
      return headlineDao.findHeadlineDetail(hid);
    } dao层中浏览量+1:
    @Override
    public int incrPageViews(int hid) {
      String sql = "update news_headline set page_views = page_views+1 where hid = ?";
      return baseUpdate(sql, hid);
    } 查询头条详情需要利用sql语句的多表查询:(即将多个表格中一样的属性作为条件拼接多张表格)设计语法有left,其含义为左连接。
    @Override
    public HeadlineDetailVo findHeadlineDetail(int hid) {
      /*需要返回的实体类
            private Integer hid;
            private String title;
            private String article;
            private Integer type;
            private String typeName;
            private Integer pageViews;
            private Long pastHours;
            private Integer publisher;
            private String author;
         */
      //此处涉及多表查询问题
      String sql = """
                select
                  h.hid hid ,
                  h.title title ,
                  h.article article ,
                  h.type type ,
                  t.tname typeName ,
                  h.page_views pageViews ,
                  TIMESTAMPDIFF(HOUR,create_time,now()) pastHours ,
                  h.publisher publisher ,
                  u.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
                  h.hid = ?
                """;
      List<HeadlineDetailVo> list = baseQuery(HeadlineDetailVo.class, sql, hid);
      return null != list && list.size() > 0 ? list.get(0) : null;
    } 用户对本身发布的消息举行增删改业务

登录校验:

在修改本身发布的消息之前需要先向服务端发送一个是否还处在登岸状态的请求(即:token有没有逾期),如果token已经逾期则无法完成修改。
https://i-blog.csdnimg.cn/direct/db77bc2d43c94d92a9be48cfc6383f31.png
NewsUserController:     /**
   * 前端自己校验是否失去登录状态的接口
   * @param req
   * @param resp
   * @throws ServletException
   * @throws IOException
   */
    protected void checkLogin(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      String token = req.getHeader("token");
      Result result = Result.build(null, ResultCodeEnum.NOTLOGIN);
      if(null != token){
            if(!JwtHelper.isExpiration(token)){
                result = Result.ok(null);
            }
      }
      WebUtil.writeJson(resp, result);
    } 注意:但是会有一个题目就是后端留好了这个校验是否处在登录状态的接口,但是前端大概不向这个接口发请求,因此必须做一个过滤器对请求举行拦截。登录过滤器是不可以把全部的请求都拦截的,只需要拦截NewsHeadlineController下的请求,因此需要注解配置。如果拦在PortalController和NewsUserController那就会变成必须要登录才能检察消息,不符合业务需求。因此拦在NewsHeadlineController。
LoginFilter:
import com.ryy.headline.common.Result;
import com.ryy.headline.common.ResultCodeEnum;
import com.ryy.headline.util.JwtHelper;
import com.ryy.headline.util.WebUtil;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebFilter("/headline/*")
public class LoginFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
      HttpServletRequest request = (HttpServletRequest) servletRequest;
      HttpServletResponse response = (HttpServletResponse) servletResponse;
      String token = request.getHeader("token");

      boolean flag = null != token && (!JwtHelper.isExpiration(token));

      if(flag){//如果还在登陆状态就放行到Controller层继续执行接下去的代码
            filterChain.doFilter(servletRequest,servletResponse);
      }else{
            WebUtil.writeJson(response, Result.build(null, ResultCodeEnum.NOTLOGIN));
      }
    }
} 修改头条并回显:

即用户点击修改之后需要向后端发送一个请求返回对应的头条详情并举行修改。
    /**
   * 修改头条回显业务接口
   * @param req
   * @param resp
   * @throws ServletException
   * @throws IOException
   */
    protected void findHeadlineByHid(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      Integer hid = Integer.parseInt(req.getParameter("hid"));
      NewsHeadline headline = headlineService.findByHid(hid);

      //注意这里前端要求的返回格式是键值对形式
      Map data = new HashMap();
      data.put("headline", headline);
      WebUtil.writeJson(resp, Result.ok(data));
    } DAO层(NewsHeadlineDaoImpl)中得sql:
    public NewsHeadline findByHid(Integer hid) {
      String sql = """
                select
                  hid,
                  title,
                  article,
                  type,
                  publisher,
                  page_views pageViews,
                  create_time createTime,
                  update_time updateTime,
                  is_deleted isDeleted
                from
                  news_headline
                where
                  hid = ?
                """;
      List<NewsHeadline> list = baseQuery(NewsHeadline.class, sql, hid);
      return null != list && list.size() > 0 ? list.get(0) : null;
    } 保存修改业务:
    /**
   * 修改头条并保存进入数据库的接口实现
   * @param req
   * @param resp
   * @throws ServletException
   * @throws IOException
   */
    protected void update(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      NewsHeadline newsHeadline = WebUtil.readJson(req, NewsHeadline.class);
      headlineService.update(newsHeadline);

      WebUtil.writeJson(resp, Result.ok(null));
    }
 DAO层的sql:
    @Override
    public int update(NewsHeadline newsHeadline) {
      String sql = """
                update
                  news_headline
                set
                  title = ?,
                  article = ?,
                  type = ?,
                  update_time = now()
                where
                  hid = ?
                """;
      return baseUpdate(sql,
                newsHeadline.getTitle(),
                newsHeadline.getArticle(),
                newsHeadline.getType(),
                newsHeadline.getHid()
      );
    } 删除业务:

末了的删除业务实际上是一个修改状态的操作,即把is_deleted属性设置为1,因为前面查询消息的这些业务逻辑都是查is_deleted=0的消息。
这里提供dao层写法:
    @Override
    public int removeByHid(int hid) {
      String sql = "update news_headline set is_deleted = 1 where hid = ?";
      return baseUpdate(sql, hid);
    }

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 微头条(全栈进阶项目,主要偏向于后端)