Day1 项目介绍
概述
命名不规范:类名首字母小写、方法名、成员变量名大写
电商系统。
后台管理系统(主要面向的是工作者而设计的一个系统,主要是用来维护前台用户系统里面的相关数据信息)
管理员模块(登录当前后台管理系统的账号):当前后台管理系统中的所有管理员账号。对其进行增删改车等操作。
点击+按钮,可以进入一个框,输入对应的信息之后,可以新增一个账号信息。
点击修改按钮,首先进行一个信息的回显(将管理员的信息再次显示出来,主要是用来提升用户体验的),再次点击保存修改,可以将信息进行修改。
删除按钮,可以删除当前管理员账号信息。
还可以根据管理员账号、昵称组合在一起进行搜索。
用户管理(顾客的信息):主要是维护着前台用户系统的账户。比如你咨询客服你的账户信息,那么她就可以看到你的相关账号信息。
主要是显示操作、删除
商品管理(主要是维护着前台用户系统里面的商品信息,没有第三方商家的概念的,全部都是自营的)
新增发布商品,发布完商品之后,那么应当在前台用户系统中可以看到该商品信息。
编辑商品,同样先是进入到商品信息回显步骤,继续在该页面操作,可以对商品进行编辑。
删除商品,将商品删除。
订单管理(主要维护着客户下的订单):
显示:将订单的信息显示出来。可以利用多条件来进行分页显示数据。
编辑:同样是对订单信息进行回显。订单的编辑修改你只需要去处理订单的状态即可。虽然页面中订单的规格、数量是可以选择的,但是你在操作的时候直接忽略即可。因为在实际的场景下,也是不允许修改订单的规格以及数量的。大家只需要去处理订单的状态变化即可。
删除:可以删除订单。删除订单你可以选择物理删除,或者逻辑删除。物理删除就是在数据库表里面的确删除了改行数据,逻辑删除其实就是相当于设置了一个标志位,让用户以为自己删除了订单,实际没有删除。
留言管理:在前台用户系统的商品详情页中咨询了一个问题,那么会在后台管理系统的留言管理中显示出来,显示出一条未回复的留言。商家可以对该留言进行回答,回答问之后,该条留言会从未回复变成已回复,同时前台用户系统中商品的该条留言也会有相应的回复(留言也只是针对商家的留言,并没有咨询已购买客户的这么一个功能)
修改资料:对当前登录的管理员账号进行密码的修改操作。
前台用户系统(主要面向的是用户,大家平常比较熟悉的电商网站)
系统架构
本项目是采用的前后端分离(页面跳转忘了)的模式来设计的。前后端分离的概念究竟指的是啥呢?
前端设计的页面系统和后端提供的数据系统不在一个系统中。
前端指的就是页面等
后端提供数据
进行交互
Referer:防盗链、谷歌的广告、抖音的推广,初创公司投放广告推广,抖音、快手
大家需要关注的是红色的部分,需要大家实现的也是红色的部分。
项目如何开展
在企业中,项目如何展开。
企业中的人员组成。
企业有很多部分,我们所属的应该叫做类似于技术部。
里面的组成也很多。很多项目组同时在进行。你可能是属于某个项目组的
针对其中某一个项目组人员组成:
项目经理:全权负责项目的进度
产品经理:需求文档
UI:主要是用来去画图的,画出用户看到的界面
前端开发:将图变成静态html页面,css样式渲染、js赋予动画效果(如果一个前端开发还会node,那么它也可以做后端)
后端开发:我们接下来大家面试的岗位。主要就是用来组装提供数据,或者接收前端传输过来的数据,保存到数据库。我们今后主要和前端开发进行对接。
DBA:(部分大公司会有。负责数据库的优化调优)
测试:并不是说一定每个项目组都会有,可能是多个项目组公用测试开发人员。
安全性测试(网络安全):部分公司可能会有。金融公司
运维:也是多个项目组公用的。
一个项目组:1-2名前端开发;3名左右后端开发
项目如何开发?
自研(公司自己去立项,几个部分一起确定要做什么事情,比如微信)、外包(根据甲方来)
立项完之后首先是需求评审,最终产品经理需要将需求确定下来,写需求文档。
首先UI画图------->前端开发人员进行操作,于此同时后端开发人员,也可以开始工作了,这个时候你需要去设计表,需要去提供数据给前端开发人员。
涉及到对接的问题。
比如页面需要数据,数据应该具有什么样的格式?数据里面的字段应该叫做什么,比如用户名应该叫做username还是name。如果提供的数据和需要的数据字段名称、数据类型不一致,那么肯定取不出来数据。对接的问题。
如何高效的对接呢?
涉及到一个叫做接口文档。注意:此接口不是指的是java里面的interface,但是从功能上去理解,其实是一个含义。java interface : Animal animal = new Pig(); animal.run();
只需要执行接口的某个方法那么就可以拿到对应的结果,我并不需要知道处理过程是什么样的、类似于一个黑盒的概念。举例比如JDBC里面的几个核心的类其实全部都是接口。
接口文档里面所说的接口的概念其实和上述介绍的非常类似,类似于一个黑盒。向指定的地址发起一个请求,携带好对应的请求参数,那么就可以返回对应的结果,就称之为一个接口。其实一个HTTP请求响应过程,我们就称之为接口。
接口文档非常的重要。一般情况下,前后端进行对接时,主要就是通过接口文档来进行对接。接口文档中应当写清楚请求的相关信息,以及返回的响应的相关信息。
前端开发主要关注于如何发送一个请求,以及接口文档中响应的数据的格式。
接口文档一般是由后端开发人员来写。也不一定。swagger:很方便的生成接口文档的工具。建议大家认真对待这个事情,自己每写完一个接口,那么把接口文档写出来。
项目一如何展开?
项目一不可能按照企业的开发流程来,不可能给大家配置一个前端来进行对接,所以我们采取的方式:预先先在网上先搭建好了一套完整的系统,代码逻辑已经实现好了的;接下来,大家需要做的事情,就是在本地的环境下,将网上的系统加以复现。其实主要是通过抓包,让本地8084后端返回的数据和网上返回的数据格式保持一致即可。
前端代码完整提供给大家,大家需要做的事情,就是本地复现出网上8084的接口返回结果。
前端代码分析
主要关注前端代码的src\api目录
admin.js—后台管理系统的接口
client.js-----前台用户系统的接口
/api/admin/admin/login
/后台管理系统/管理员模块/管理员模块下的登录功能
export function login(data){
const res = axios.post(‘/api/admin/admin/login’,data);
点击登录按钮时,调用该方法login方法
会往这个地址发起一个HTTP请求,/api/admin/admin/login,携带data请求参数
其实不知道发往哪个主机、哪个端口号,在哪配置呢?
src\config目录下,有俩文件
一个是axios-admin.js:axios的后台接口设置
axios-client.js:axios的前台用户接口设置
axios.defaults.baseURL = ‘http://localhost:8084’;
其实主机端口号的部分就是指的是这里
也就是说点击后台管理系统的登录按钮时
前端会往http://localhost:8084/api/admin/admin/login发起post请求,同时携带一个请求参数,需要8084系统返回一个结果,这个结果应当按照什么样的格式呢?如果在企业中需要前端后端进行协商,确定对接的格式;在项目一,你需要做的事情想方设法和网上的现有系统(http://192.168.2.100:8080)提供的响应结果保持一致即可。
http://192.168.2.100:8080/ 对应的是提供给大家的前端代码,这个你不用管
http://192.168.2.100:8084 对应的是你本地的http://localhost:8084系统,这个系统是需要大家去实现的,如何去实现,和2.100上面的返回json字符串(响应体)格式结构相同。
前端代码如何处理
前端代码在今后企业开发过程中,实际上不需要你过分担心。
1.在package.json所在的目录执行cnpm install
如果你的电脑中cnpm显示没有安装,或者安装不成功,那么可以执行如下指令:
npm config set registry https://registry.npm.taobao.org
这一步的效果相当于将原先的npm镜像修改为国内镜像,更改完之后,再次执行
npm install 即可。
2.执行npm run dev,应当会打开一个服务器,监听8080端口号(config/index.js)
http://localhost:8080/admin.html(一定要从后台管理系统开始,不要自己擅自从前台用户系统开始开发)
编写服务端代码
主要是针对8084的系统。新建一个Maven的EE项目。
新建一个Servlet
package com.cskaoyan.mall.controller;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 用来处理后台管理系统的管理员模块的增删改查等操作
*/
@WebServlet("/api/admin/admin/*")
public class AdminServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
访问http://localhost:8085/admin.html,点击登录,会往http://localhost:8084/api/admin/admin/login发起一个post请求,此时抓包显示CORS错误。
表示的是跨域失败。
页面访问的是http://localhost:8080
紧接着发现又往http://localhost:8084发起请求
跨域的,默认屏蔽的,感觉行为可能不安全。如果希望能够跨域,此时需要8084服务器返回一些特殊的响应头。
跨域时请求的流程是:首先先发送一个OPTIONS请求,你可以认为这个是一个试探性请求,如果该OPTIONS请求能够拿到有效的响应头,那么就会去发送真正的请求。每次跨域时都会采取相同的处理方式。所以关于设置跨域的响应头部分的代码可以写在filter中。
response.setHeader("Access-Control-Allow-Origin","*");
response.setHeader("Access-Control-Allow-Methods","POST,GET,OPTIONS,PUT,DELETE");
response.setHeader("Access-Control-Allow-Headers","x-requested-with,Authorization,Content-Type");
response.setHeader("Access-Control-Allow-Credentials","true");
登录接口
需求分析:
管理员输入用户名、密码,如果输入正确, 那么可以进入到该系统;如果用户名、密码错误,那么无法进入该系统。
转换成技术点:
点击登录按钮,发送一个HTTP请求报文,会携带用户输入的用户名、密码,传输到服务器上面,解析成request对象
服务器需要做什么?
取出请求参数,到数据库中进行校验比对,返回一个结果给页面。
抓包:
请求报文
POST http://192.168.2.100:8084/api/admin/login HTTP/1.1
Host: 192.168.2.100:8084
Connection: keep-alive
Content-Length: 34
Accept: application/json, text/plain, */*
Authorization: Bearer admin
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36 Edg/92.0.902.78
Content-Type: application/json;charset=UTF-8
Origin: http://192.168.2.100:8080
Referer: http://192.168.2.100:8080/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
{"email":"admin","pwd":"admin123"}
请求体中携带了请求参数,可不可以使用request.getParameter来获取参数?不可以。不是key=value
你应该如何获取请求参数?
1.拿到请求体,解析成字符串
2.将字符串解析成java对象
响应报文:
用户名、密码错误
HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Access-Control-Allow-Origin: *
Content-Type: application/json;charset=UTF-8
Date: Wed, 25 Aug 2021 01:50:53 GMT
Content-Length: 43
{"code":10000,"message":"密码不正确!"}
登录成功:
HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Access-Control-Allow-Origin: *
Content-Type: application/json;charset=UTF-8
Date: Wed, 25 Aug 2021 01:53:31 GMT
Content-Length: 50
{"code":0,"data":{"token":"admin","name":"admin"}}
接口中的对象
BO:business object。请求中传递过来的java对象
POJO:该对象和数据库里面的字段应当是一一对应的。
VO:显示给页面的对象。想显示商品的信息(商品里面应当嵌套另一个规格的集合对象)
bo里面的字段叫做email和pwd,那么该字段和数据库表中我们对应的字段名称有没有关联?没有
BO------->POJO-----------> VO
数据保存在数据库,是不是应该有一个java类和数据库里面的数据一一对应 ,比如用户数据------数据库 用户信息
当客户端发送也给请求时,请求参数里面的字段信息和数据库里面的字段信息必须要保持一致吗?
{"email":"admin","pwd":"admin123"}
此时用户发送的请求参数时email和pwd,那么是否意味着数据库中该字段一定也是同样的呢?不是。两者之间没有任何关联。
还有可能不同的终端发送过来的请求参数字段名都不是完全相同的。
手机端、平板端
可以一个对象在所有的场景下全部复用吗?
比如数据库表 id username password--------------------------User id、username、password
但是此时请求发送过来时请求参数的字段名叫做email、pwd,直接在user中在扩增几个字段吗?还是新建一个?新建。
页面需要的数据字段信息和数据库里面的字段信息一定要保持一致吗?不一定。新建一个对象传出去
BO:从客户端传递进来的请求参数
VO:响应给客户端的字段信息
POJO:和数据库里面的字段一一对应的
正常情况下来说,一个接口应当对应三个bean才对。如果某些场景下可以复用,那么可以i稍微复用一下。
回顾一下mybatis的过程
主配置文件mybatis.xml文件
SqlSessionFactoryBuilder-------读取配置i文件—sqlSessionFactroy—sqlSession
sqlSession.getMapper(interface);--------------就是根据接口在运行时动态的去生成一个该接口的实现类(动态代理)
adminDao.count();
如何响应
企业中,是前端开发和服务器开发人员进行对接。但是在本项目中,实现过程应当是如下:
通过去抓取局域网中的接口返回值,分析响应报文中响应体的格式,然后本地加以复现即可。
前后端的约定的规则:如果返回的是code=0,表示的是成功,返回10000,表示登录失败,登录失败的情况下,需要去取message的值
登录失败:
{“code”:10000,“message”:“密码不正确!”}
登录成功:
{“code”:0,“data”:{“token”:“admin”,“name”:“admin”}}
本项目在设计的时候其实时有一些bug的,大家在写的时候,你可以按照自己的想法设计来,功能上你可以自行取项目进行扩展,但是最终你只需要遵守响应体的格式符合要求即可。
比如管理员模块,可以设置多级管理员账号,admin时超级管理员,不允许删除;其他账户可以删除,但是也仅仅只局限于可以删除当前自己账户,无法删除其他账户
三层架构:
controller:获取请求参数,校验、调用service方法,将结果返回给前端。 4s门店
service:具体的业务逻辑都应当放在该层来做。 组装车间(零部件在组装车间进行组装)
dao:一个一个单独的sql语句。 汽车的各个不同的零部件
修改功能需求分析
点击修改按钮,那么首先触发的不是修改,而是一个信息的回显。getAdminsInfo?id=1
表示的是需要取加载当前id=1的admin信息。回显对于用户的操作更加友好。
如果在新建或者修改的过程中出现了
账户正则验证未通过(必须qq邮箱 qq号5-12位)!
其实是前端并没有去真正去发送修改或者新增的请求,在前面的校验步骤就已经失败了
package com.cskaoyan.mall.controller;
import com.alibaba.druid.util.StringUtils;
import com.cskaoyan.mall.model.Result;
import com.cskaoyan.mall.model.bo.AdminLoginBO;
import com.cskaoyan.mall.model.vo.AllAdminVO;
import com.cskaoyan.mall.model.vo.LoginVo;
import com.cskaoyan.mall.service.AdminService;
import com.cskaoyan.mall.service.AdminServiceImpl;
import com.google.gson.Gson;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
/**
* 用来处理后台管理系统的管理员模块的增删改查等操作
*/
@WebServlet("/api/admin/admin/*")
public class AdminServlet extends HttpServlet {
private Gson gson = new Gson();
private AdminService adminService = new AdminServiceImpl();
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String requestURI = request.getRequestURI();
String action = requestURI.replace(request.getContextPath() + "/api/admin/admin/", "");
if("login".equals(action)){
login(request,response);
}
}
/**
* 登录的逻辑:首先需要接收到用户提交过来的请求参数
* 将请求参数放置到数据库中进行比对,判断用户名、密码是否正确
* 返回一个结果给客户端
* @param request
* @param response
*/
private void login(HttpServletRequest request, HttpServletResponse response) throws IOException {
//请求参数位于请求体中,并且时以json字符串的形式存在
//如何拿到请求体 为什么请求体是以inputStream的形式存在
//因为请求体不仅可以存储文本类型的请求参数,还可以存储二进制类型数据 文件上传
ServletInputStream inputStream = request.getInputStream();
//ServletInputStream就可以把它当作FileInputStream来看待,只是文本的来源不同
//如何把FileInputStream变成字符串?
int length = 0;
byte[] bytes = new byte[1024];
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
while ((length = inputStream.read(bytes)) != -1){
//new String()
outputStream.write(bytes, 0,length);
}
// {"email":"admin","pwd":"admin123"}
String requestBody = outputStream.toString("utf-8");
outputStream.close();
AdminLoginBO loginBO = gson.fromJson(requestBody, AdminLoginBO.class);
//校验
if(StringUtils.isEmpty(loginBO.getEmail()) || StringUtils.isEmpty(loginBO.getPwd())){
// TODO
Result result = new Result(10000, "参数不能为空", null);
response.getWriter().println(gson.toJson(result));
return;
}
//接下来应该调用模型的方法(三层架构里面的service和dao其实就是之前model的进一步解耦)
//调用service的方法,返回结果即可
int code = adminService.login(loginBO);
Result result = null;
if(code == 200){
//登录成功
LoginVo loginVo = new LoginVo(loginBO.getEmail(), loginBO.getEmail());
result = new Result(0, null, loginVo);
}else {
//登录失败
result = new Result(10000, "用户名、密码错误", null);
}
response.getWriter().println(gson.toJson(result));
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String requestURI = request.getRequestURI();
String action = requestURI.replace(request.getContextPath() + "/api/admin/admin/", "");
if("allAdmins".equals(action)){
allAdmins(request,response);
}
}
/**
* 逻辑:1.获取请求参数(没有)
* 2.执行当前接口的具体业务逻辑:数据库里面的管理员账户信息全部返回给前端 JDBC
* 3.返回结果
* {"code":0,"data":[{"id":1,"email":"admin","nickname":"admin","pwd":"admin"}]}
* [{"id":1,"email":"admin","nickname":"admin","pwd":"admin"}]
* @param request
* @param response
*/
private void allAdmins(HttpServletRequest request, HttpServletResponse response) throws IOException {
List<AllAdminVO> adminVOS = adminService.allAdmins();
Result result = new Result(0, null, adminVOS);
response.getWriter().println(gson.toJson(result));
}
}
package com.cskaoyan.mall.service;
import com.cskaoyan.mall.model.bo.AdminLoginBO;
import com.cskaoyan.mall.model.vo.AllAdminVO;
import java.util.List;
public interface AdminService {
int login(AdminLoginBO loginBO);
List<AllAdminVO> allAdmins();
}
package com.cskaoyan.mall.service;
import com.cskaoyan.mall.dao.AdminDao;
import com.cskaoyan.mall.model.Admin;
import com.cskaoyan.mall.model.bo.AdminLoginBO;
import com.cskaoyan.mall.model.vo.AllAdminVO;
import com.cskaoyan.mall.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import java.util.ArrayList;
import java.util.List;
public class AdminServiceImpl implements AdminService {
@Override
public int login(AdminLoginBO loginBO) {
//需要读取mybatis.xml文件
SqlSession sqlSession = MybatisUtils.openSession();
AdminDao adminDao = sqlSession.getMapper(AdminDao.class);
Admin admin = new Admin(null, loginBO.getEmail(), loginBO.getPwd(), null);
int count = adminDao.count(admin);
//即便时查询,也要把sqlSession给关闭,否则会出现死锁问题
//sqlSession一定不要写成成员变量,不能复用
sqlSession.close();
if(count == 1){
return 200;
}
return 404;
}
@Override
public List<AllAdminVO> allAdmins() {
//sqlSession一定不能设置成成员变量
SqlSession sqlSession = MybatisUtils.openSession();
AdminDao adminDao = sqlSession.getMapper(AdminDao.class);
List<Admin> adminList = adminDao.allAdmins();
sqlSession.close();
List<AllAdminVO> adminVOS = new ArrayList<>();
for (Admin admin : adminList) {
AllAdminVO adminVO = new AllAdminVO(admin.getId(), admin.getUsername(), admin.getPassword(), admin.getNickname());
adminVOS.add(adminVO);
}
return adminVOS;
}
}
package com.cskaoyan.mall.dao;
import com.cskaoyan.mall.model.Admin;
import java.util.List;
public interface AdminDao {
//这个方法时用来进行管理员登录使用的,controller中传的参数时loginBo
//那么在这需要传LoginBo还是Admin呢?
//建议传Admin?可复用性会很好
int count(Admin admin);
List<Admin> allAdmins();
}
Day2 动态sql
管理员新增功能:
点击+,如果输入的信息不符合要求,显示正则验证未通过?
点击保存修改,触发一个事件(函数):首先对参数进行校验,看是否符合要求,不符合规则的条件下,直接就返回,不会去使用axios发送HTTP请求。
无论前端还是服务端都应该对数据进行校验?为什么呢?
前端的页面是非常容易跳过的。
我只需要记住前端接下来发送的请求的地址以及请求的方式,其实完全可以自己去工具去发送这样的一个请求,那么如果你的代码中只是前端做了校验,那么就可以直接跳过校验。
postman、还可以写代码。
如果发现2.100上面的请求会携带id,但是你本地的没有携带id,为什么?
前端页面上面显示的任何数据都是你传给前端的,如果没有,那么往前追溯
遇到bug一般我是如何处理的?
1.排查状态码;如果failed(跨域失败、后端服务器异常(一般先执行mvn clean操作,再次执行mvn package操作));如果是500,去找错误日志(server/output、localhost log;自己去try catch )
如果数据库报错,如何排查?
1.JDBC sql
先排查sql,首先先执行sql,看结果正不正常,不正常,是sql的问题,和JDBC没有关系
如果sql正常,JDBC最终的执行结果不正常,那么mybatis(无参构造函数)
建议都用debug,不要使用run模式,使用sout输出
如果感觉代码没问题,但是运行结果始终不对,尝试去看一下maven有没有问题。
mvn clean
mvn package-------能否出现build success
动态sql:
根据输入的账户和昵称搜索相关联的管理员信息。
将通过HTTP请求报文提交过来的json字符串类型的请求参数解析,获取,接下来到数据库进行查询、比对。对应的sql语句为
select * from admin;
select * from admin where email like ?;
select * from admin where nickname like ?;
select * from admin where email like ? and nickname like ?;
核心的知识点就是mybatis的动态sql。where标签、if标签
package com.cskaoyan.mall.controller;
import com.alibaba.druid.util.StringUtils;
import com.cskaoyan.mall.model.Result;
import com.cskaoyan.mall.model.bo.AddAdminBO;
import com.cskaoyan.mall.model.bo.AdminLoginBO;
import com.cskaoyan.mall.model.bo.SearchAdminBO;
import com.cskaoyan.mall.model.vo.AllAdminVO;
import com.cskaoyan.mall.model.vo.LoginVo;
import com.cskaoyan.mall.service.AdminService;
import com.cskaoyan.mall.service.AdminServiceImpl;
import com.cskaoyan.mall.utils.HttpUtils;
import com.google.gson.Gson;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
/**
* 用来处理后台管理系统的管理员模块的增删改查等操作
*/
@WebServlet("/api/admin/admin/*")
public class AdminServlet extends HttpServlet {
private Gson gson = new Gson();
private AdminService adminService = new AdminServiceImpl();
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String requestURI = request.getRequestURI();
String action = requestURI.replace(request.getContextPath() + "/api/admin/admin/", "");
if("login".equals(action)){
login(request,response);
}else if("addAdminss".equals(action)){
addAdminss(request, response);
}else if("getSearchAdmins".equals(action)){
getSearchAdmins(request, response);
}
}
/**
* 通过输入账户和昵称可以对管理员进行模糊查询
* @param request
* @param response
*/
private void getSearchAdmins(HttpServletRequest request, HttpServletResponse response) throws IOException {
String requestBody = HttpUtils.getRequestBody(request);
SearchAdminBO searchAdminBO = gson.fromJson(requestBody, SearchAdminBO.class);
List<AllAdminVO> adminVOS = adminService.searchAdmin(searchAdminBO);
response.getWriter().println(gson.toJson(Result.ok(adminVOS)));
}
/**
* 新增管理员账号的逻辑:
* 1.获取到请求参数-----key=value json字符串
* 2.执行具体的业务逻辑----保存到数据库
* 3.返回一个结果
* {"nickname":"admin1234","email":"66678@qq.com","pwd":"Aa12345%"}
* @param request
* @param response
*/
private void addAdminss(HttpServletRequest request, HttpServletResponse response) throws IOException {
String requestBody = HttpUtils.getRequestBody(request);
AddAdminBO addAdminBO = gson.fromJson(requestBody, AddAdminBO.class);
//无论前端还是服务端都应该对数据进行校验?为什么呢?
//对前端传递过来的参数进行校验 正则 TODO
int code = adminService.addAdmin(addAdminBO);
if(code == 200){
//成功
response.getWriter().println(gson.toJson(Result.ok()));
}else {
//失败
response.getWriter().println(gson.toJson(Result.error("当前用户名已经被占用")));
}
}
/**
* 登录的逻辑:首先需要接收到用户提交过来的请求参数
* 将请求参数放置到数据库中进行比对,判断用户名、密码是否正确
* 返回一个结果给客户端
* @param request
* @param response
*/
private void login(HttpServletRequest request, HttpServletResponse response) throws IOException {
//请求参数位于请求体中,并且时以json字符串的形式存在
//如何拿到请求体 为什么请求体是以inputStream的形式存在
//因为请求体不仅可以存储文本类型的请求参数,还可以存储二进制类型数据 文件上传
String requestBody = HttpUtils.getRequestBody(request);
AdminLoginBO loginBO = gson.fromJson(requestBody, AdminLoginBO.class);
//校验
if(StringUtils.isEmpty(loginBO.getEmail()) || StringUtils.isEmpty(loginBO.getPwd())){
// TODO
Result result = new Result(10000, "参数不能为空", null);
response.getWriter().println(gson.toJson(result));
return;
}
//接下来应该调用模型的方法(三层架构里面的service和dao其实就是之前model的进一步解耦)
//调用service的方法,返回结果即可
int code = adminService.login(loginBO);
Result result = null;
if(code == 200){
//登录成功
LoginVo loginVo = new LoginVo(loginBO.getEmail(), loginBO.getEmail());
result = new Result(0, null, loginVo);
}else {
//登录失败
result = new Result(10000, "用户名、密码错误", null);
}
response.getWriter().println(gson.toJson(result));
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String requestURI = request.getRequestURI();
String action = requestURI.replace(request.getContextPath() + "/api/admin/admin/", "");
if("allAdmins".equals(action)){
allAdmins(request,response);
}else if("getAdminsInfo".equals(action)){
getAdminsInfo(request, response);
}
}
/**
* 通过id去取得当前管理员账号的详细信息
* @param request
* @param response
*/
private void getAdminsInfo(HttpServletRequest request, HttpServletResponse response) throws IOException {
String id = request.getParameter("id");
//校验
AllAdminVO adminVO = adminService.getAdmin(Integer.parseInt(id));
response.getWriter().println(gson.toJson(Result.ok(adminVO)));
}
/**
* 逻辑:1.获取请求参数(没有)
* 2.执行当前接口的具体业务逻辑:数据库里面的管理员账户信息全部返回给前端 JDBC
* 3.返回结果
* {"code":0,"data":[{"id":1,"email":"admin","nickname":"admin","pwd":"admin"}]}
* [{"id":1,"email":"admin","nickname":"admin","pwd":"admin"}]
* @param request
* @param response
*/
private void allAdmins(HttpServletRequest request, HttpServletResponse response) throws IOException {
List<AllAdminVO> adminVOS = adminService.allAdmins();
Result result = new Result(0, null, adminVOS);
response.getWriter().println(gson.toJson(result));
}
}
package com.cskaoyan.mall.service;
import com.cskaoyan.mall.dao.AdminDao;
import com.cskaoyan.mall.model.Admin;
import com.cskaoyan.mall.model.bo.AddAdminBO;
import com.cskaoyan.mall.model.bo.AdminLoginBO;
import com.cskaoyan.mall.model.bo.SearchAdminBO;
import com.cskaoyan.mall.model.vo.AllAdminVO;
import com.cskaoyan.mall.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import java.util.ArrayList;
import java.util.List;
public class AdminServiceImpl implements AdminService {
@Override
public int login(AdminLoginBO loginBO) {
//需要读取mybatis.xml文件
SqlSession sqlSession = MybatisUtils.openSession();
AdminDao adminDao = sqlSession.getMapper(AdminDao.class);
Admin admin = new Admin(null, loginBO.getEmail(), loginBO.getPwd(), null);
int count = adminDao.count(admin);
//即便时查询,也要把sqlSession给关闭,否则会出现死锁问题
//sqlSession一定不要写成成员变量,不能复用
sqlSession.commit();
sqlSession.close();
if(count == 1){
return 200;
}
return 404;
}
@Override
public List<AllAdminVO> allAdmins() {
//sqlSession一定不能设置成成员变量
SqlSession sqlSession = MybatisUtils.openSession();
AdminDao adminDao = sqlSession.getMapper(AdminDao.class);
List<Admin> adminList = adminDao.allAdmins(new Admin());
sqlSession.commit();
sqlSession.close();
List<AllAdminVO> adminVOS = new ArrayList<>();
for (Admin admin : adminList) {
AllAdminVO adminVO = new AllAdminVO(admin.getId(), admin.getUsername(), admin.getPassword(), admin.getNickname());
adminVOS.add(adminVO);
}
return adminVOS;
}
@Override
public int addAdmin(AddAdminBO addAdminBO) {
SqlSession sqlSession = MybatisUtils.openSession();
AdminDao adminDao = sqlSession.getMapper(AdminDao.class);
Admin admin = new Admin(null, addAdminBO.getEmail(), addAdminBO.getPwd(), addAdminBO.getNickname());
try {
adminDao.addAdmin(admin);
return 200;
}catch (Exception e){
}finally {
sqlSession.commit();
sqlSession.close();
}
return 500;
}
@Override
public AllAdminVO getAdmin(int id) {
SqlSession sqlSession = MybatisUtils.openSession();
AdminDao adminDao = sqlSession.getMapper(AdminDao.class);
Admin admin = new Admin(id, null, null, null);
List<Admin> adminList = adminDao.allAdmins(admin);
sqlSession.commit();
sqlSession.close();
admin = adminList.get(0);
AllAdminVO adminVO = new AllAdminVO(admin.getId(), admin.getUsername(), admin.getPassword(), admin.getNickname());
return adminVO;
}
@Override
public List<AllAdminVO> searchAdmin(SearchAdminBO searchAdminBO) {
SqlSession sqlSession = MybatisUtils.openSession();
AdminDao adminDao = sqlSession.getMapper(AdminDao.class);
Admin admin = new Admin(null, searchAdminBO.getEmail(), null, searchAdminBO.getNickname());
List<Admin> adminList = adminDao.allAdmins(admin);
List<AllAdminVO> adminVOS = new ArrayList<>();
for (Admin ad : adminList) {
AllAdminVO adminVO = new AllAdminVO(ad.getId(), ad.getUsername(), ad.getPassword(), ad.getNickname());
adminVOS.add(adminVO);
}
return adminVOS;
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cskaoyan.mall.dao.AdminDao">
<select id="count" parameterType="com.cskaoyan.mall.model.Admin" resultType="java.lang.Integer">
select count(*) from admin where username = #{username} and password = #{password}
</select>
<select id="allAdmins" resultType="com.cskaoyan.mall.model.Admin" parameterType="com.cskaoyan.mall.model.Admin">
select id,username,password,nickname from admin
<where>
<if test="id != null">
id = #{id}
</if>
<if test="username != null and username != ''">
and username like concat("%", #{username}, "%")
</if>
<if test="nickname != null and nickname != ''">
and nickname like concat("%", #{nickname}, "%")
</if>
</where>
</select>
<insert id="addAdmin" parameterType="com.cskaoyan.mall.model.Admin">
insert into admin values (null, #{username}, #{password}, #{nickname})
</insert>
</mapper>
Day3 商品管理
如何debug
三层架构如何打断点
代码是如何执行的?
最简单的方式就是在程序的执行入口打一个断点,程序一步一步往下走(step over),遇到调用service的代码,执行force step into
controller调用service,service调用dao
后台管理系统中可以对前台的商品进行管理,比如发布、修改等,那么相对应的前台系统中商品信息也会随着同步发生修改。
实际上它们之间的关联就是通过数据库表,一个修改表,一个查询表。
三层架构代码如何编写:
1.每个模块写一个controller,命名 见名知意。AdminServlet 或者AdminController,同时配套写一个service、dao
2.每新建一个新的controller,或者开启一个新的模块,那么都需要适配写一个新的service、dao
类目、商品、规格其实都可以写在一个servlet中。
如果添加类目,最终显示在页面上的名称中文乱码,如何分析,如何debug?
1.大体可以将整个过程先分为两个阶段,保存到数据库之前,一个是保存到数据库之后;
2.通过去debug排查,看一下究竟在哪个阶段它就已经出现了乱码问题。打断点------让程序执行。断点的作用是当程序执行的时候,运行到改行代码,那么会停顿下来。
can not initialize MybatisUtil
点击添加类目,返回的类目中文乱码,如何排查?
数据保存到数据库------------数据库-----------数据库取出来显示给页面
接下来去新建商品表和规格表
一对多的关系。关系的维护写在多的一方(规格)
商品表:
id
name
typeId
image
description
price ------最低 如果商品表不存price,需要在查询的时候进行连接查询;如果商品表存price,直接查询单表即可;新增或者修改的时候,需要去维护一下数据即可
stockNum----顺带维护一个stockNum
规格表:
id
goodsId
name
stockNum
price
新增商品:
关于文件上传,一定不要去执行读取流转换成字符串操作,否则文件直接损坏。
新增商品的逻辑:
1.获取请求参数,并将其解析成为java对象。保存到商品表(price、stockNum),在specList中的,将list里面的数据取出来迭代,取出最低价格,stockNum之和
2.将数据保存到规格表(里面需要goodsId)
3.按照约定的格式返回响应的响应数据(抓取现有系统的响应体)
怎么获取goods的id呢?因为商品表的id是自增的,是交给数据库去维护的,如何获取到goods 的id呢?
controller:数据的获取、封装、校验等,调用service代码逻辑,返回结果
service:具体的业务逻辑细节都应该在service层
dao:负责一个一个的方法
删除类目:自己思考决定如何实现该功能?1.如果下面有商品,那么不能删除 2.可以删除,全部都删除 3.可以删除类目,但是商品不删除,可以设置一个缺省的分类,如果类目删除了,把商品移到缺省类目中
删除商品的同时肯定也需要把规格一并删除
package com.cskaoyan.mall.controller;
import com.cskaoyan.mall.model.Result;
import com.cskaoyan.mall.model.Type;
import com.cskaoyan.mall.model.bo.AddGoodsBO;
import com.cskaoyan.mall.model.vo.GetGoodsByTypeVO;
import com.cskaoyan.mall.service.GoodsService;
import com.cskaoyan.mall.service.GoodsServiceImpl;
import com.cskaoyan.mall.utils.FileUploadUtils;
import com.cskaoyan.mall.utils.HttpUtils;
import com.google.gson.Gson;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Map;
@WebServlet("/api/admin/goods/*")
public class GoodsServlet extends HttpServlet {
private Gson gson = new Gson();
private GoodsService goodsService = new GoodsServiceImpl();
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String requestURI = request.getRequestURI();
String action = requestURI.replace(request.getContextPath() + "/api/admin/goods/", "");
if("imgUpload".equals(action)){
imgUpload(request, response);
}else if("addGoods".equals(action)){
addGoods(request, response);
}
}
private void addGoods(HttpServletRequest request, HttpServletResponse response) throws IOException {
//1.获取请求参数 请求体 json字符串
String requestBody = HttpUtils.getRequestBody(request);
AddGoodsBO addGoodsBO = null;
try {
addGoodsBO = gson.fromJson(requestBody, AddGoodsBO.class);
}catch (Exception e){
response.getWriter().println(gson.toJson(Result.error("参数不合法")));
return;
}
goodsService.addGoods(addGoodsBO);
response.getWriter().println(gson.toJson(Result.ok()));
}
/**
* 还是使用我们之前介绍的commons-fileupload来处理即可
* @param request
* @param response
*/
private void imgUpload(HttpServletRequest request, HttpServletResponse response) throws IOException {
Map<String, Object> map = FileUploadUtils.parseRequest(request);
String file = (String) map.get("file");
response.getWriter().println(gson.toJson(Result.ok(file)));
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String requestURI = request.getRequestURI();
String action = requestURI.replace(request.getContextPath() + "/api/admin/goods/", "");
if("getType".equals(action)){
getType(request, response);
}else if("getGoodsByType".equals(action)){
getGoodsByType(request, response);
}
}
/**
* 后台管理系统中查询某个分类下商品信息的接口逻辑
* 1.获取请求参数typeId
* @param request
* @param response
*/
private void getGoodsByType(HttpServletRequest request, HttpServletResponse response) throws IOException {
String typeId = request.getParameter("typeId");
//校验
List<GetGoodsByTypeVO> goodsByTypeVOS = goodsService.getGoodsByType(Integer.parseInt(typeId));
response.getWriter().println(gson.toJson(Result.ok(goodsByTypeVOS)));
}
private void getType(HttpServletRequest request, HttpServletResponse response) throws IOException {
List<Type> typeList = goodsService.getType();
response.getWriter().println(gson.toJson(Result.ok(typeList)));
}
}
package com.cskaoyan.mall.service;
import com.cskaoyan.mall.dao.GoodsDao;
import com.cskaoyan.mall.model.Goods;
import com.cskaoyan.mall.model.Spec;
import com.cskaoyan.mall.model.Type;
import com.cskaoyan.mall.model.bo.AddGoodsBO;
import com.cskaoyan.mall.model.bo.AddGoodsSpecBO;
import com.cskaoyan.mall.model.vo.GetGoodsByTypeVO;
import com.cskaoyan.mall.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import java.util.ArrayList;
import java.util.List;
public class GoodsServiceImpl implements GoodsService {
@Override
public List<Type> getType() {
SqlSession sqlSession = MybatisUtils.openSession();
GoodsDao goodsDao = sqlSession.getMapper(GoodsDao.class);
List<Type> types = goodsDao.getType();
sqlSession.commit();
sqlSession.close();
return types;
}
@Override
public List<GetGoodsByTypeVO> getGoodsByType(int typeId) {
SqlSession sqlSession = MybatisUtils.openSession();
GoodsDao goodsDao = sqlSession.getMapper(GoodsDao.class);
List<Goods> goodsList = goodsDao.getGoodsByType(typeId);
sqlSession.commit();
sqlSession.close();
List<GetGoodsByTypeVO> goodsByTypeVOS = new ArrayList<>();
for (Goods goods : goodsList) {
goodsByTypeVOS.add(new GetGoodsByTypeVO(goods.getId(),goods.getName(), goods.getTypeId(),goods.getImage(), goods.getPrice(), goods.getStockNum()));
}
return goodsByTypeVOS;
}
/**
* 事务:一组操作,要么全部成功,要么全部不成功
* ACID:原子性、一致性、隔离性、持久性
*隔离性更为重要一些:多个事务并发访问数据库时,数据库给每个事务提供互不干扰的一种保障
* 问题:脏读(一个事务读取了另外一个事务未提交的事务)、不可重复读(在一个事务内前后进行两次查询,查询的结果不一致,读取的是另外事务已提交的数据)、虚幻读(前后条数不一致 新增)
* mysql的repeatable read可以屏蔽三种问题 快照
* @param addGoodsBO
*/
@Override
public void addGoods(AddGoodsBO addGoodsBO) {
List<AddGoodsSpecBO> list = addGoodsBO.getSpecList();
int stockNum = 0;
double price = Double.MAX_VALUE;
for (AddGoodsSpecBO addGoodsSpecBO : list) {
if(addGoodsSpecBO.getUnitPrice() < price){
price = addGoodsSpecBO.getUnitPrice();
}
stockNum += addGoodsSpecBO.getStockNum();
}
SqlSession sqlSession = MybatisUtils.openSession();
GoodsDao goodsDao = sqlSession.getMapper(GoodsDao.class);
Goods goods = new Goods(null, addGoodsBO.getName(), addGoodsBO.getTypeId(), addGoodsBO.getImg(), addGoodsBO.getDesc(), price, stockNum);
//只需要执行完插入语句,那么goods的id就会封装到原先的goods对象中
goodsDao.addGoods(goods);
Integer goodsId = goods.getId();
List<Spec> specList = new ArrayList<>();
for (AddGoodsSpecBO addGoodsSpecBO : list) {
//做一个转换,将addGoodsSpecBo-----Spec
specList.add(new Spec(null, goodsId, addGoodsSpecBO.getSpecName(), addGoodsSpecBO.getStockNum(), addGoodsSpecBO.getUnitPrice()));
}
int i = 1 / 0;
goodsDao.addSpeces(specList);
sqlSession.commit();
sqlSession.close();
}
}
package com.cskaoyan.mall.dao;
import com.cskaoyan.mall.model.Goods;
import com.cskaoyan.mall.model.Spec;
import com.cskaoyan.mall.model.Type;
import java.util.List;
public interface GoodsDao {
List<Type> getType();
List<Goods> getGoodsByType(int typeId);
void addGoods(Goods goods);
void addSpeces(List<Spec> specList);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cskaoyan.mall.dao.GoodsDao">
<select id="getType" resultType="com.cskaoyan.mall.model.Type">
select id,name from type;
</select>
<select id="getGoodsByType" parameterType="java.lang.Integer" resultType="com.cskaoyan.mall.model.Goods">
select id,name,typeId,image,description,price,stockNum from goods where typeId = #{typeId}
</select>
<insert id="addGoods" useGeneratedKeys="true" keyProperty="id" parameterType="com.cskaoyan.mall.model.Goods">
insert into goods values (null, #{name}, #{typeId}, #{image}, #{description}, #{price}, #{stockNum})
</insert>
<insert id="addSpeces" parameterType="com.cskaoyan.mall.model.Spec">
insert into spec values
<foreach collection="list" item="sp" separator=",">
(null, #{sp.goodsId}, #{sp.name}, #{sp.stockNum}, #{sp.price})
</foreach>
</insert>
</mapper>
package com.cskaoyan.mall.model.bo;
import java.util.List;
public class AddGoodsBO {
private String img;
private String name;
private Integer typeId;
private String desc;
private List<AddGoodsSpecBO> specList;
public void setImg(String img) {
this.img = img;
}
public void setName(String name) {
this.name = name;
}
public Integer getTypeId() {
return typeId;
}
public void setTypeId(Integer typeId) {
this.typeId = typeId;
}
public void setDesc(String desc) {
this.desc = desc;
}
public String getImg() {
return img;
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
public List<AddGoodsSpecBO> getSpecList() {
return specList;
}
public void setSpecList(List<AddGoodsSpecBO> specList) {
this.specList = specList;
}
}
package com.cskaoyan.mall.model.bo;
public class AddGoodsSpecBO {
private Double unitPrice;
private String specName;
private Integer stockNum;
public Double getUnitPrice() {
return unitPrice;
}
public void setUnitPrice(Double unitPrice) {
this.unitPrice = unitPrice;
}
public String getSpecName() {
return specName;
}
public void setSpecName(String specName) {
this.specName = specName;
}
public Integer getStockNum() {
return stockNum;
}
public void setStockNum(Integer stockNum) {
this.stockNum = stockNum;
}
}
package com.cskaoyan.mall.model.vo;
/**
* int和Integer的区别是啥? 基本类型 包装类型 java 面向对象
* 默认赋值 int---0 Integer null
*/
public class GetGoodsByTypeVO {
private Integer id;
private String name;
private Integer typeId;
private String img;
private Double price;
private Integer stockNum;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getTypeId() {
return typeId;
}
public void setTypeId(Integer typeId) {
this.typeId = typeId;
}
public String getImg() {
return img;
}
public void setImg(String img) {
this.img = img;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public Integer getStockNum() {
return stockNum;
}
public void setStockNum(Integer stockNum) {
this.stockNum = stockNum;
}
public GetGoodsByTypeVO(Integer id, String name, Integer typeId, String img, Double price, Integer stockNum) {
this.id = id;
this.name = name;
this.typeId = typeId;
this.img = "http://localhost:8084/" + img;
this.price = price;
this.stockNum = stockNum;
}
public GetGoodsByTypeVO() {
}
}
Day4 订单管理
订单新增的入口在前台用户系统。可以先新建表,自己先预先插入几条数据。
修改订单的规格和数量,基本是不符合业务场景需求的。
修改订单只实现修改订单状态即可。
订单删除逻辑:可以选择物理删除(从表中实实在在删除该条记录)、或者逻辑删除(设置一个状态位,让用户觉得删除了该订单,其实只是对其不可见罢了)。
订单其实可以暴露一个人很多信息。用户行为画像、人物画像。 推荐算法。
订单表如何创建?
本商城在设计接口的时候,其实是没有购物车表,意味着加入购物车里面的商品其实是相当于在订单表中下了一个未付款的订单。
一个订单中永远只有一个商品。
意味着本系统在设计订单表的时候不需要去设计订单详情页,所以,需求还简单了一些。
收货地址信息以及订单中的商品信息应该在订单表中存入完整的信息。
你在购物某商品时,使用的是某个地址,后面你把改地址给改了,那么会出现在配送过程中,突然地址发生变化的场景吗?
你在10年前买了一台iphone4,你去查看该订单,是否还在?订单里面的商品信息是否还在?通过订单进入该商品,发现商品不在了
订单其实是某个时刻行为的一个记录。618大促 买了手机,比平时贵了100块钱,活动结束之后 ,看订单,金额是多少呢?
id
nickname
receiver
address
phone
product
productId
spec
specId
number
amount
stateId
createtime
updatetime
hasComment
JavaBean:
对象。成员变量private、提供public的get和set方法。无参构造函数。
框架在封装数据到对象中,都会去调用无参构造函数来实例化一个对象,紧接着调用set方法来完成赋值。
有的框架,比较智能,比如mybatis,如果在没有提供无参构造函数的情况下,会调用有参构造函数来封装数据,此时如果数据库查询查到的数据个数、数据的类型和构造函数要求的信息不一致,就会发生异常。
package com.cskaoyan.mall.controller;
import com.alibaba.druid.util.StringUtils;
import com.cskaoyan.mall.model.Result;
import com.cskaoyan.mall.model.bo.OrdersByPageBO;
import com.cskaoyan.mall.model.vo.OrdersByPageVO;
import com.cskaoyan.mall.service.OrderService;
import com.cskaoyan.mall.service.OrderServiceImpl;
import com.cskaoyan.mall.utils.HttpUtils;
import com.google.gson.Gson;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/api/admin/order/*")
public class OrderServlet extends HttpServlet {
private Gson gson = new Gson();
private OrderService orderService = new OrderServiceImpl();
//如果不晓得如何打断点,那么就在程序的入口处打断点
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String requestURI = request.getRequestURI();
String action = requestURI.replace(request.getContextPath() + "/api/admin/order/", "");
if("ordersByPage".equals(action)){
ordersByPage(request, response);
}
}
private void ordersByPage(HttpServletRequest request, HttpServletResponse response) throws IOException {
String requestBody = HttpUtils.getRequestBody(request);
OrdersByPageBO ordersByPageBO = gson.fromJson(requestBody, OrdersByPageBO.class);
//判断 金额上下限可以不填,但是如果填的话,那么一定要求是数字
try {
if(!StringUtils.isEmpty(ordersByPageBO.getMoneyLimit1())){
Double.parseDouble(ordersByPageBO.getMoneyLimit1());
}
if(!StringUtils.isEmpty(ordersByPageBO.getMoneyLimit2())){
Double.parseDouble(ordersByPageBO.getMoneyLimit2());
}
}catch (Exception e){
response.getWriter().println(gson.toJson(Result.error("金额必须是数字")));
return;
}
OrdersByPageVO orders = orderService.ordersByPage(ordersByPageBO);
response.getWriter().println(gson.toJson(Result.ok(orders)));
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
package com.cskaoyan.mall.service;
import com.cskaoyan.mall.dao.OrderDao;
import com.cskaoyan.mall.model.bo.OrdersByPageBO;
import com.cskaoyan.mall.model.vo.OrdersByPageInnerOrderVO;
import com.cskaoyan.mall.model.vo.OrdersByPageVO;
import com.cskaoyan.mall.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import java.util.List;
public class OrderServiceImpl implements OrderService {
//接下来需要做哪些事情?
//1.查询当前条件下符合搜索条件的总记录数
//2.当前页的具体条目信息
// sqlSession.commit(); sqlSession.rollBack();
@Override
public OrdersByPageVO ordersByPage(OrdersByPageBO ordersByPageBO) {
SqlSession sqlSession = MybatisUtils.openSession();
OrderDao orderDao = sqlSession.getMapper(OrderDao.class);
int count = orderDao.getTotalCount(ordersByPageBO);
List<OrdersByPageInnerOrderVO> orders = orderDao.ordersByPage(ordersByPageBO);
sqlSession.commit();
sqlSession.close();
OrdersByPageVO ordersByPageVO = new OrdersByPageVO();
ordersByPageVO.setTotal(count);
ordersByPageVO.setOrders(orders);
return ordersByPageVO;
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cskaoyan.mall.dao.OrderDao">
<select id="getTotalCount" parameterType="com.cskaoyan.mall.model.bo.OrdersByPageBO" resultType="java.lang.Integer">
select count(*) from orders
<where>
<if test="id != null and id != ''">
id = #{id}
</if>
<if test="name != null and name != ''">
and receiver like concat("%", #{name}, "%")
</if>
<if test="address != null and address != ''">
and address like concat("%", #{address}, "%")
</if>
<if test="moneyLimit1 != null and moneyLimit1 != ''">
and amount <= #{moneyLimit1}
</if>
<if test="moneyLimit2 != null and moneyLimit2 != ''">
and amount >= #{moneyLimit2}
</if>
<if test="state != -1">
and stateId = #{state}
</if>
</where>
</select>
<select id="ordersByPage" parameterType="com.cskaoyan.mall.model.bo.OrdersByPageBO" resultType="com.cskaoyan.mall.model.vo.OrdersByPageInnerOrderVO">
select id,userId,specId as goodsDetailId,product as goods,spec,number as goodsNum,amount,stateId,nickname,receiver as name, address,phone from orders
<where>
<if test="id != null and id != ''">
id = #{id}
</if>
<if test="name != null and name != ''">
and receiver like concat("%", #{name}, "%")
</if>
<if test="address != null and address != ''">
and address like concat("%", #{address}, "%")
</if>
<if test="moneyLimit1 != null and moneyLimit1 != ''">
and amount <= #{moneyLimit1}
</if>
<if test="moneyLimit2 != null and moneyLimit2 != ''">
and amount >= #{moneyLimit2}
</if>
<if test="state != -1">
and stateId = #{state}
</if>
</where>
limit #{pagesize} offset ${(currentPage - 1) * pagesize}
</select>
</mapper>
package com.cskaoyan.mall.model.bo;
public class OrdersByPageBO {
private String address;
private String moneyLimit2;
private String moneyLimit1;
private Integer pagesize;
private String name;
private String goods;
private Integer state;
private String id;
private Integer currentPage;
public void setAddress(String address) {
this.address = address;
}
public void setMoneyLimit2(String moneyLimit2) {
this.moneyLimit2 = moneyLimit2;
}
public void setMoneyLimit1(String moneyLimit1) {
this.moneyLimit1 = moneyLimit1;
}
public void setPagesize(int pagesize) {
this.pagesize = pagesize;
}
public void setName(String name) {
this.name = name;
}
public void setGoods(String goods) {
this.goods = goods;
}
public void setState(int state) {
this.state = state;
}
public void setId(String id) {
this.id = id;
}
public void setCurrentPage(int currentPage) {
this.currentPage = currentPage;
}
public String getAddress() {
return address;
}
public String getMoneyLimit2() {
return moneyLimit2;
}
public String getMoneyLimit1() {
return moneyLimit1;
}
public int getPagesize() {
return pagesize;
}
public String getName() {
return name;
}
public String getGoods() {
return goods;
}
public int getState() {
return state;
}
public String getId() {
return id;
}
public int getCurrentPage() {
return currentPage;
}
}
package com.cskaoyan.mall.model.vo;
public class OrdersByPageInnerOrderUserVO {
private String address;
private String phone;
private String nickname;
private String name;
public void setAddress(String address) {
this.address = address;
}
public void setPhone(String phone) {
this.phone = phone;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public String getPhone() {
return phone;
}
public String getNickname() {
return nickname;
}
public String getName() {
return name;
}
}
package com.cskaoyan.mall.model.vo;
public class OrdersByPageInnerOrderVO {
private Double amount;
private Integer goodsDetailId;
private Integer stateId;
private String goods;
private Integer id;
private String state;
private Integer userId;
private Integer goodsNum;
private String spec;
private OrdersByPageInnerOrderUserVO user = new OrdersByPageInnerOrderUserVO();
private String nickname;
private String name;
private String address;
private String phone;
public void setNickname(String nickname) {
// this.nickname = nickname;
user.setNickname(nickname);
}
public void setName(String name) {
// this.name = name;
user.setName(name);
}
public void setAddress(String address) {
// this.address = address;
user.setAddress(address);
}
public void setPhone(String phone) {
// this.phone = phone;
user.setPhone(phone);
}
public Double getAmount() {
return amount;
}
public void setAmount(Double amount) {
this.amount = amount;
}
public Integer getGoodsDetailId() {
return goodsDetailId;
}
public void setGoodsDetailId(Integer goodsDetailId) {
this.goodsDetailId = goodsDetailId;
}
public Integer getStateId() {
return stateId;
}
public void setStateId(Integer stateId) {
this.stateId = stateId;
if(stateId == 0){
setState("未付款");
}else if(stateId == 1){
setState("未发货");
}else if(stateId == 2){
setState("已发货");
}else if(stateId == 3){
setState("已到货");
}
}
public String getGoods() {
return goods;
}
public void setGoods(String goods) {
this.goods = goods;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public Integer getGoodsNum() {
return goodsNum;
}
public void setGoodsNum(Integer goodsNum) {
this.goodsNum = goodsNum;
}
public String getSpec() {
return spec;
}
public void setSpec(String spec) {
this.spec = spec;
}
public OrdersByPageInnerOrderUserVO getUser() {
return user;
}
public void setUser(OrdersByPageInnerOrderUserVO user) {
this.user = user;
}
}
package com.cskaoyan.mall.model.vo;
import java.util.List;
public class OrdersByPageVO {
private Integer total;
private List<OrdersByPageInnerOrderVO> orders;
public Integer getTotal() {
return total;
}
public void setTotal(Integer total) {
this.total = total;
}
public List<OrdersByPageInnerOrderVO> getOrders() {
return orders;
}
public void setOrders(List<OrdersByPageInnerOrderVO> orders) {
this.orders = orders;
}
}
Day5 权限管理
我们针对的主要是8084系统,也就是服务端系统的接口需要进行权限验证,只有当登录了管理员账号,才可以调用该接口,如果没有登录的情况下,是无法调用该接口的。
比如http://localhost:8084/api/admin/admin/allAdmins
如何加?
AdminSevlet中添加如下代码:
private void login(HttpServletRequest request, HttpServletResponse response) throws IOException {
//请求参数位于请求体中,并且时以json字符串的形式存在
//如何拿到请求体 为什么请求体是以inputStream的形式存在
//因为请求体不仅可以存储文本类型的请求参数,还可以存储二进制类型数据 文件上传
String requestBody = HttpUtils.getRequestBody(request);
AdminLoginBO loginBO = gson.fromJson(requestBody, AdminLoginBO.class);
//校验
if(StringUtils.isEmpty(loginBO.getEmail()) || StringUtils.isEmpty(loginBO.getPwd())){
// TODO
Result result = new Result(10000, "参数不能为空", null);
response.getWriter().println(gson.toJson(result));
return;
}
//接下来应该调用模型的方法(三层架构里面的service和dao其实就是之前model的进一步解耦)
//调用service的方法,返回结果即可
int code = adminService.login(loginBO);
Result result = null;
if(code == 200){
//登录成功
LoginVo loginVo = new LoginVo(loginBO.getEmail(), loginBO.getEmail());
result = new Result(0, null, loginVo);
//写入session
request.getSession().setAttribute("username", loginBO.getEmail());
}else {
//登录失败
result = new Result(10000, "用户名、密码错误", null);
}
response.getWriter().println(gson.toJson(result));
}
Filter中代码:
package com.cskaoyan.mall.filter;
import com.cskaoyan.mall.model.Result;
import com.google.gson.Gson;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebFilter("/api/*")
// CorsFilter + AuthFilter
public class ApplicationFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
//表示哪些主机可以访问当前的系统
response.setHeader("Access-Control-Allow-Origin","*");
response.setHeader("Access-Control-Allow-Methods","POST,GET,OPTIONS,PUT,DELETE");
response.setHeader("Access-Control-Allow-Headers","x-requested-with,Authorization,Content-Type");
response.setHeader("Access-Control-Allow-Credentials","true");
//取session
//哪些是需要验证登录,哪些是不需要验证登录?
String requestURI = request.getRequestURI();
String uri = requestURI.replace(request.getContextPath(), "");
if(!"/api/admin/admin/login".equals(uri)){
//如果请求的地址不是/api/admin/admin/login那么表示的是需要验证登录状态
String username = (String) request.getSession().getAttribute("username");
if(username == null){
//需要登录后才可以访问,但是此时没有登录
response.getWriter().println(new Gson().toJson(Result.error("当前接口仅允许登录后才可以访问")));
return;
}
}
filterChain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
结果发现登录后依然无法访问到接口的内容?
意味着session中没有取到数据---------->猜想:session不是一个session对象?(验证)---------> 请求没有携带cookie:JSESSIONID=xxx(抓包)
网络迷踪
为什么发送请求时,没有携带cookie呢?
跨域。跨域时,默认情况下浏览器是不允许携带cookie的,如果希望浏览器能够携带cookie,则需要前端代码和服务端代码同时做一些设置修改:
前端:
针对axios-admin.js以及axios-client.js文件,新增如下代码:
axios.defaults.withCredentials = true
服务端代码做如下设置:(前端所在的域名)
response.setHeader("Access-Control-Allow-Origin","http://localhost:8080");
此外,此时还有一些有问题
通过在filter中打log,追踪
OPTIONS /api/admin/admin/loginorg.apache.catalina.session.StandardSessionFacade@2e6e9993
POST /api/admin/admin/loginorg.apache.catalina.session.StandardSessionFacade@321d1645
login:org.apache.catalina.session.StandardSessionFacade@321d1645
login:FA9DAEDCC183E64226901B5AB194DCFB
OPTIONS /api/admin/admin/allAdminsorg.apache.catalina.session.StandardSessionFacade@38070b83
GET /api/admin/admin/allAdminsorg.apache.catalina.session.StandardSessionFacade@321d1645
每次发送OPTIONS请求时,依然会去创建一个新的session对象,发送真正的请求,是不会创建session对象的
发送OPTIONS请求时,不会携带cookie的(试探性请求,是不会携带cookie的)
发送真正的请求,携带了cookie
package com.cskaoyan.mall.filter;
import com.cskaoyan.mall.model.Result;
import com.google.gson.Gson;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebFilter("/api/*")
// CorsFilter + AuthFilter
public class ApplicationFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
//表示哪些主机可以访问当前的系统
response.setHeader("Access-Control-Allow-Origin","http://localhost:8085");
response.setHeader("Access-Control-Allow-Methods","POST,GET,OPTIONS,PUT,DELETE");
response.setHeader("Access-Control-Allow-Headers","x-requested-with,Authorization,Content-Type");
response.setHeader("Access-Control-Allow-Credentials","true");
//取session
//哪些是需要验证登录,哪些是不需要验证登录?
String requestURI = request.getRequestURI();
String uri = requestURI.replace(request.getContextPath(), "");
//System.out.println(request.getMethod() + " " + requestURI + request.getSession());
String method = request.getMethod();
if(!"OPTIONS".equals(method)){
if(!"/api/admin/admin/login".equals(uri)){
//如果请求的地址不是/api/admin/admin/login那么表示的是需要验证登录状态
HttpSession session = request.getSession();
String username = (String) session.getAttribute("username");
if(username == null){
//需要登录后才可以访问,但是此时没有登录
response.getWriter().println(new Gson().toJson(Result.error("当前接口仅允许登录后才可以访问")));
return;
}
}
}
filterChain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
紧接着转入前台。
转入前台之后,因为之前的filter设置的url-pattern是/api/*,意味着访问前台时,必须后台管理系统人员登录之后才可以访问到前台数据。很明显不合理。给前台单独设置一个filter。
package com.cskaoyan.mall.filter;
import com.cskaoyan.mall.model.Result;
import com.google.gson.Gson;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebFilter("/api/admin/*")
// CorsFilter + AuthFilter
public class AdminFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
//表示哪些主机可以访问当前的系统
response.setHeader("Access-Control-Allow-Origin","http://localhost:8085");
response.setHeader("Access-Control-Allow-Methods","POST,GET,OPTIONS,PUT,DELETE");
response.setHeader("Access-Control-Allow-Headers","x-requested-with,Authorization,Content-Type");
response.setHeader("Access-Control-Allow-Credentials","true");
//取session
//哪些是需要验证登录,哪些是不需要验证登录?
String requestURI = request.getRequestURI();
String uri = requestURI.replace(request.getContextPath(), "");
//System.out.println(request.getMethod() + " " + requestURI + request.getSession());
String method = request.getMethod();
if(!"OPTIONS".equals(method)){
if(!"/api/admin/admin/login".equals(uri)){
//如果请求的地址不是/api/admin/admin/login那么表示的是需要验证登录状态
HttpSession session = request.getSession();
String username = (String) session.getAttribute("username");
if(username == null){
//需要登录后才可以访问,但是此时没有登录
response.getWriter().println(new Gson().toJson(Result.error("当前接口仅允许登录后才可以访问")));
return;
}
}
}
filterChain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
专门用来处理后台管理系统的接口
再新建一个MallFilter
package com.cskaoyan.mall.filter;
import com.cskaoyan.mall.model.Result;
import com.google.gson.Gson;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebFilter("/api/mall/*")
// CorsFilter + AuthFilter
public class MallFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
//表示哪些主机可以访问当前的系统
response.setHeader("Access-Control-Allow-Origin","http://localhost:8085");
response.setHeader("Access-Control-Allow-Methods","POST,GET,OPTIONS,PUT,DELETE");
response.setHeader("Access-Control-Allow-Headers","x-requested-with,Authorization,Content-Type");
response.setHeader("Access-Control-Allow-Credentials","true");
filterChain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
处理前台的逻辑。
通过去抓包
http://localhost:8084/api/mall/index/getType
发现接口的返回值和后台管理系统的返回值是一模一样的。
新建一个新的Servlet,然后service、dao直接复用现成的即可。
package com.cskaoyan.mall.controller.mall;
import com.cskaoyan.mall.model.Result;
import com.cskaoyan.mall.model.Type;
import com.cskaoyan.mall.service.GoodsService;
import com.cskaoyan.mall.service.GoodsServiceImpl;
import com.google.gson.Gson;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
@WebServlet("/api/mall/index/*")
public class IndexServlet extends HttpServlet {
private GoodsService goodsService = new GoodsServiceImpl();
private Gson gson = new Gson();
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String requestURI = request.getRequestURI();
String action = requestURI.replace(request.getContextPath() + "/api/mall/index/", "");
if("getType".equals(action)){
getType(request, response);
}
}
private void getType(HttpServletRequest request, HttpServletResponse response) throws IOException {
List<Type> type = goodsService.getType();
response.getWriter().println(gson.toJson(Result.ok(type)));
}
}
转入前台之后,原则:
新建servlet,但是service、dao可以直接复用,如果不能够复用,那么就创建新的方法到原先的service中、原先的dao中
比如首页获取商品信息,写到原先GoodsService中
针对前台商品详情页中的商品评价
需要你返回score来表示商品的好评。分数必须要求是20的整数倍,如果不是,没法渲染图标。
优化点:
针对filter中设置的
response.setHeader(“Access-Control-Allow-Origin”,“http://localhost:8085”);
及
public GetGoodsByTypeVO(Integer id, String name, Integer typeId, String img, Double price, Integer stockNum) {
this.id = id;
this.name = name;
this.typeId = typeId;
this.img = "http://localhost:8084/" + img;
this.price = price;
this.stockNum = stockNum;
}
以硬编码的形式存储下来的。
针对跨域部分的内容,需要将服务端代码改成
response.setHeader(“Access-Control-Allow-Origin”,“http://你的ip地址:8085”);
前端代码需要修改:
axios.defaults.baseURL = ‘http://你的ip地址:8084’;
将服务端部分的地址信息以配置文件的形式配置下来。
package com.cskaoyan.mall.listener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
@WebListener
public class ApplicationListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
//读取properties配置文件里面的内容,然后放入context域
InputStream inputStream = ApplicationListener.class.getClassLoader().getResourceAsStream("application.properties");
Properties properties = new Properties();
try {
properties.load(inputStream);
String origin = properties.getProperty("origin");
servletContextEvent.getServletContext().setAttribute("origin", origin);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
}
}
package com.cskaoyan.mall.filter;
import com.cskaoyan.mall.model.Result;
import com.google.gson.Gson;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebFilter("/api/admin/*")
// CorsFilter + AuthFilter
public class AdminFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
//表示哪些主机可以访问当前的系统
ServletContext servletContext = request.getServletContext();
String origin = (String) servletContext.getAttribute("origin");
response.setHeader("Access-Control-Allow-Origin",origin);
response.setHeader("Access-Control-Allow-Methods","POST,GET,OPTIONS,PUT,DELETE");
response.setHeader("Access-Control-Allow-Headers","x-requested-with,Authorization,Content-Type");
response.setHeader("Access-Control-Allow-Credentials","true");
//取session
//哪些是需要验证登录,哪些是不需要验证登录?
String requestURI = request.getRequestURI();
String uri = requestURI.replace(request.getContextPath(), "");
//System.out.println(request.getMethod() + " " + requestURI + request.getSession());
String method = request.getMethod();
if(!"OPTIONS".equals(method)){
if(!"/api/admin/admin/login".equals(uri)){
//如果请求的地址不是/api/admin/admin/login那么表示的是需要验证登录状态
HttpSession session = request.getSession();
String username = (String) session.getAttribute("username");
if(username == null){
//需要登录后才可以访问,但是此时没有登录
response.getWriter().println(new Gson().toJson(Result.error("当前接口仅允许登录后才可以访问")));
return;
}
}
}
filterChain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
package com.cskaoyan.mall.filter;
import com.cskaoyan.mall.model.Result;
import com.google.gson.Gson;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebFilter("/api/mall/*")
// CorsFilter + AuthFilter
public class MallFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
//表示哪些主机可以访问当前的系统
ServletContext servletContext = request.getServletContext();
String origin = (String) servletContext.getAttribute("origin");
response.setHeader("Access-Control-Allow-Origin",origin);
response.setHeader("Access-Control-Allow-Methods","POST,GET,OPTIONS,PUT,DELETE");
response.setHeader("Access-Control-Allow-Headers","x-requested-with,Authorization,Content-Type");
response.setHeader("Access-Control-Allow-Credentials","true");
filterChain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
Day6 正则表达式
前台也需要加入权限验证
加入购物车、下单购买、个人中心 这些接口 也必须登录之后才可以访问到(判断用户是否登录)
改成使用ip地址来进行登录:
1.后端配置修改:
localhost------>ip地址
2.前端配置修改:
src/config/axios-admin.js以及axios-client.js
axios.defaults.baseURL = ‘http://192.168.4.72:8084’;
3.访问前端页面时,不要使用localhost来访问,改成使用ip地址
http://192.168.4.72:8080/admin.html#/login
package com.cskaoyan.mall;
import com.alibaba.druid.util.StringUtils;
import java.util.Scanner;
public class RegTest {
public static void main(String[] args) {
//判断用户输入的是否是6位数字
Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine();
//boolean result = isValid(input);
boolean result = isValidByReg(input);
System.out.println(result);
}
private static boolean isValidByReg(String input) {
return input.matches("\\d{6}");
}
/**
* 判断用户输入的是否是6位数字
* @param input
* @return
*/
private static boolean isValid(String input) {
if(StringUtils.isEmpty(input)){
return false;
}
if(input.length() != 6){
return false;
}
try {
Integer.parseInt(input);
return true;
}catch (Exception e){
}
return false;
}
}
package com.cskaoyan.mall;
import com.alibaba.druid.util.StringUtils;
import java.util.Scanner;
public class RegTest {
public static void main(String[] args) {
//判断用户输入的是否是6位数字
Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine();
//boolean result = isValid(input);
boolean result = isValidByReg(input);
System.out.println(result);
}
private static boolean isValidByReg(String input) {
//return input.matches("\\d{6}");
//正则表达式最简单的用法就是用来判断和给定字符一模一样的字符串
//return input.matches("123456");
//次数限定符只作用于最邻近的一个字符 z zo zoo zooooo....
// return input.matches("zo*");
//如果想匹配zozo,应该怎么做?
// return input.matches("(zo)*");
// return input.matches("go{2,}gle");
//匹配z或者food,如果想匹配zood或者food,需要将(z|f)
// return input.matches("z|food");
// return input.matches("(z|f)ood");
//1.只能匹配一位 2.这一位必须是abcd中的一个
// return input.matches("[abcd]");
//匹配26个字母呢
// return input.matches("[a-z]");
//1. 只能匹配一位 2.这一位不能是abcd中的任意一个
// return input.matches("[^abcd]");
// ^如果在[]里面,表示的是取反,如果不在[]里面,表示的是匹配开始的位置
// return input.matches("^hello$");
// return input.matches("\\d{6}");
//想匹配一个网址 www.baidu.com
//在正则中.表示的是匹配任意一位字符,如果想匹配.,那么需要对其进行转义 \ \\(java)
return input.matches("www\\.baidu\\.com");
}
/**
* 判断用户输入的是否是6位数字
* @param input
* @return
*/
private static boolean isValid(String input) {
if(StringUtils.isEmpty(input)){
return false;
}
if(input.length() != 6){
return false;
}
try {
Integer.parseInt(input);
return true;
}catch (Exception e){
}
return false;
}
}
前台的filter也是需要拦截一些东西的。
个人主页的信息。如果用户没有登录,那么直接访问用户的个人主页应当是不予展示的,加入购物车、下单等接口。
前台针对的是用户的操作(如果用户没有登录,那么和用户信息相关的内容应当是不能显示的)
后台针对的是admin的操作(因为后台管理系统所有的接口都必须要求admin管理员登录了之后才可以操作)