项目一
Day1 项目介绍
概述
电商系统。
后台管理系统(主要面向的是工作者而设计的一个系统,主要是用来维护前台用户系统里面的相关数据信息)
管理员模块(登录当前后台管理系统的账号):当前后台管理系统中的所有管理员账号。对其进行增删改车等操作。
点击+按钮,可以进入一个框,输入对应的信息之后,可以新增一个账号信息。
点击修改按钮,首先进行一个信息的回显(将管理员的信息再次显示出来,主要是用来提升用户体验的),再次点击保存修改,可以将信息进行修改。
删除按钮,可以删除当前管理员账号信息。
还可以根据管理员账号、昵称组合在一起进行搜索。
用户管理(顾客的信息):主要是维护着前台用户系统的账户。比如你咨询客服你的账户信息,那么她就可以看到你的相关账号信息。
主要是显示操作、删除
商品管理(主要是维护着前台用户系统里面的商品信息,没有第三方商家的概念的,全部都是自营的)
新增发布商品,发布完商品之后,那么应当在前台用户系统中可以看到该商品信息。
编辑商品,同样先是进入到商品信息回显步骤,继续在该页面操作,可以对商品进行编辑。
删除商品,将商品删除。
订单管理(主要维护着客户下的订单):
显示:将订单的信息显示出来。可以利用多条件来进行分页显示数据。
编辑:同样是对订单信息进行回显。订单的编辑修改你只需要去处理订单的状态即可。虽然页面中订单的规格、数量是可以选择的,但是你在操作的时候直接忽略即可。因为在实际的场景下,也是不允许修改订单的规格以及数量的。大家只需要去处理订单的状态变化即可。
删除:可以删除订单。删除订单你可以选择物理删除,或者逻辑删除。物理删除就是在数据库表里面的确删除了改行数据,逻辑删除其实就是相当于设置了一个标志位,让用户以为自己删除了订单,实际没有删除。
留言管理:在前台用户系统的商品详情页中咨询了一个问题,那么会在后台管理系统的留言管理中显示出来,显示出一条未回复的留言。商家可以对该留言进行回答,回答问之后,该条留言会从未回复变成已回复,同时前台用户系统中商品的该条留言也会有相应的回复(留言也只是针对商家的留言,并没有咨询已购买客户的这么一个功能)
修改资料:对当前登录的管理员账号进行密码的修改操作。
前台用户系统(主要面向的是用户,大家平常比较熟悉的电商网站)
系统架构
本项目是采用的前后端分离的模式来设计的。前后端分离的概念究竟指的是啥呢?
前端设计的页面系统和后端提供的数据系统不在一个系统中。
项目如何开展
在企业中,项目如何展开。
企业中的人员组成。
企业有很多部分,我们所属的应该叫做类似于技术部。
里面的组成也很多。很多项目组同时在进行。你可能是属于某个项目组的
针对其中某一个项目组人员组成:
项目经理:全权负责项目的进度
产品经理:需求文档
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-----前台用户系统的接口
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://115.29.141.32:8084)提供的响应结果保持一致即可。
http://115.29.141.32:8085/ 对应的是提供给大家的前端代码,这个你不用管
http://115.29.141.32:8084 对应的是你本地的http://localhost:8084系统,这个系统是需要大家去实现的,如何去实现,copy一份即可。115如何返回数据,你如何返回数据即可。
前端代码如何处理
前端代码在今后企业开发过程中,实际上不需要你过分担心。
1.在package.json所在的目录执行cnpm install
如果你的电脑中cnpm显示没有安装,或者安装不成功,那么可以执行如下指令:
npm config set registry https://registry.npm.taobao.org
这一步的效果相当于将原先的npm镜像修改为国内镜像,更改完之后,再次执行
npm install 即可。
2.执行npm run dev,应当会打开一个服务器,监听8085端口号,你需要啊打开链接
http://localhost:8085/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:8085
紧接着发现又往http://localhost:8084发起请求
跨域的,默认屏蔽的,感觉行为可能不安全。如果希望能够跨域,此时需要8084服务器返回一些特殊的响应头。
跨域时请求的流程是:首先先发送一个OPTIONS请求,你可以认为这个是一个试探性请求,如果该OPTIONS请求能够拿到有效的响应头,那么就会去发送真正的请求。每次跨域时都会采取相同的处理方式。所以关于设置跨域的响应头部分的代码可以写在filter中。
登录接口
需求分析:
管理员输入用户名、密码,如果输入正确, 那么可以进入到该系统;如果用户名、密码错误,那么无法进入该系统。
转换成技术点:
点击登录按钮,发送一个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"}}
接口中的对象
数据保存在数据库,是不是应该有一个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();
}