微头条项目开发
在进行微头条项目开发的时候,学习到了新的知识点,在此记录下来
Postman测试工具
Postman API Platform | Sign Up for Free
接口
不同与java中的interface接口文件,我们在这里要提到的是前端 -> 后端的业务接口
在我们目前编写的微头条项目中,前端访问业务接口的形式是通过不同的URI来实现的
在后端中,Controller层定义了不同的业务接口,通过BaseController工具类反射到WebServlet注解上
通过注解的模糊匹配的方式,实现不同的URI访问不同的业务方法
我们在后端代码编写时,可通过postman测试工具来测试接口功能
Token
toker中文释义:令牌
使用传统的Session和Cookie的模式,在并发问题中会有大量的服 务器开销,我们选择Token来解决问题
在验证用户名和密码正确无误后,后端将业务码(200)响应给客户端的同时,将用户信息加密成Token,一起响应给客户端
当客户端再发送请求时,就拿着加密的token解析用户信息,这样客户端就完全不知道用户的信息了
JWT工具类
我们使用JWT工具类对Token进行加密和解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| package com.xiaobai.headline.util;
import com.alibaba.druid.util.StringUtils; import io.jsonwebtoken.*;
import java.util.Date;
public class JwtUtil { private static long tokenExpiration = 24*60*60*1000; private static String tokenSignKey = "827724";
public static String createToken(Long userId) { String token = Jwts.builder()
.setSubject("YYGH-USER") .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration)) .claim("userId", userId) .signWith(SignatureAlgorithm.HS512, tokenSignKey) .compressWith(CompressionCodecs.GZIP) .compact(); return token; }
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(); }
public static boolean isExpiration(String token){ try { boolean isExpire = Jwts.parser() .setSigningKey(tokenSignKey) .parseClaimsJws(token) .getBody() .getExpiration().before(new Date()); return isExpire; }catch(Exception e) { return true; } } }
|
GET和Post
GET方式发送请求,也可以使用请求体里装JSON的格式
POST方式发送请求,也可以使用URI后面的键值对形式
只不过在html的form表单中,get使用键值对,post使用JSON串
值对象
值对象(Value Object)
当我们发现,前端请求中的数据中,有不完全符合pojo层的实体类的数据,也就是不完全与数据库表中列完全对应的数据
这个时候我们接受数据,就要新建一个类,这个类就是值类(值对象)
他用于接受前端业务接口中传过来的请求参数,在操作数据库时,也可以将值对象中的不同属性分别放到不同的表中
甚至可以不放在任何一个表里
分页
这是我们第一次接触到分页的问题
前端利用响应式数据绑定了一些分页所需要的参数,这些参数大致有:
pageData:本页数据
pageNum:当前页码数
pageSize:每页显示数量
totalPage:总页数
totalSize:数据的总数
这五个参数也是大部分分页写法的五大参数,将这些参数以键值对(JSON)的方式返回给前端
但很显然,数据库中并没有这些数据,这些是由后端调用数据库获取数据进行处理后的参数
我们需要用到VO(值对象)去包装这些参数,最后转成JSON返回前端
其中,pageNum(当前页码)和pageSize(每页显示数量)由前端提供,直接返回即可
Service
这里就体现出来Controller什么都不管的性质了
Controller只负责调用Service,并且把请求中的类封装好扔进来,再接受封装好的响应类,打个包扔回前端
在Service层中,我们要给这分页五大项赋值
通过数据库查询到pageData 和 totalSize,再通过totalSize和pageSize就能算出totalPage
注:如果总条目数/每页多少条能整除,那就正好分多少页,如果不能整除,要取整除的商后加1
将五大项打包,发给Controller处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public class NewsHeadlineServiceImpl implements NewsHeadlineService { private NewsHeadLineDao newsHeadLineDao = new NewsHeadlineDaoImpl();
@Override public Map findNewsPage(HeadlineQueryVo headlineQueryVo) { int pageNum = headlineQueryVo.getPageNum(); int pageSize = headlineQueryVo.getPageSize();
List<HeadlinePageVo> pageData = newsHeadLineDao.findPageList(headlineQueryVo);
int totalSize = newsHeadLineDao.findPageCount(headlineQueryVo); int totalPage = totalSize % pageSize == 0 ? totalSize / pageSize : totalSize / pageSize + 1; Map map = new HashMap(); map.put("pageNum", pageNum); map.put("pageSize", pageSize); map.put("totalSize", totalSize); map.put("totalPage", totalPage); map.put("pageData", pageData); return map; } }
|
Dao
这个sql是我们写过最复杂的sql
因为要考虑到where参数:type=0 :所有类型都查,
和关键词没有:不设关键词条件查询
当这两个条件存在时,我们用concat为sql语句拼串,建立params集合用来存放占位符参数
要考虑pageData中内容的排序问题
最重要的是:要考虑limit参数,每次请求只请求这一页的内容,考虑从第几页的数据(条数)开始返回,还有一页返回多少条数据
注:将params列表转换为数组才能给可变长参数传参
注:在sql的语句的追加拼串上,要记得前空后空
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| package com.xiaobai.headline.dao.impl;
import com.xiaobai.headline.dao.BaseDao; import com.xiaobai.headline.dao.NewsHeadLineDao; import com.xiaobai.headline.pojo.vo.HeadlinePageVo; import com.xiaobai.headline.pojo.vo.HeadlineQueryVo;
import java.util.ArrayList; import java.util.List;
public class NewsHeadlineDaoImpl extends BaseDao implements NewsHeadLineDao { @Override public int findPageCount(HeadlineQueryVo headlineQueryVo) { List params = new ArrayList(); 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() + "%"); }
return baseQueryObject(Long.class, sql, params.toArray()).intValue(); }
@Override public List<HeadlinePageVo> findPageList(HeadlineQueryVo headlineQueryVo) { List params = new ArrayList(); 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()); } }
|
LoginFilter
在前端接手了一些操作数据的过滤后,我们后端的filter只是用来处理同源禁止策略的预检请求
但完全交给前端来做登录的验证是否合适呢?
不合适,因为这对后端,对数据都是不太安全的操作
我们后端在接收到数据后,也要用token来验证一下是否为登录状态,这样进入数据库的数据才会更加安全
所以我们编写一个LoginFilter,在进行增删改查的controller之前,做一个登录判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @WebFilter("/headline/*") public class LoginFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse) servletResponse; HttpServletRequest request = (HttpServletRequest) servletRequest; String token = request.getHeader("token");
boolean flag = null != token && !JwtUtil.isExpiration(token);
if (flag) { filterChain.doFilter(request, response); } else { WebUtil.writeJson(response, Result.build(null, ResultCodeEnum.NOTLOGIN)); } } }
|
is_deleted
数据无价,我们在数据库中发现这样一个属性:is_deleted
在进行查询业务的时候,会在判断语句里增加 is_deleted = 0,代表着这条数据没有被删除
删除数据,我们使用一个属性的0和1来代表这此数据是否被删除,而不是真正的从数据库上移除
这样就能有一个后悔药——从数据库原始数据中再次寻找被删掉的数据,相当于一个永久回收站
相比于随着科技进步的低廉存储价格,数据才是真正的无价
总结
微头条项目结束之后,我们在前端框架的帮助下,实现了后端不使用框架完成独立项目
这个项目主要是用来熟悉业务逻辑
Controller层不负责处理任何业务,只是负责req和resp
Service层用来处理具体业务
Dao层与数据库交互
以这种业务逻辑去写代码才会顺畅
接下来就是后端框架的学习,Java学习之路道阻且长,希望一切顺利!