# product-sys
**Repository Path**: zing163/product-sys
## Basic Information
- **Project Name**: product-sys
- **Description**: 商品管理系统
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2020-12-28
- **Last Updated**: 2021-03-20
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
## 一、单张表的增、删、改、查
### 1、创建项目
**src包的结构如下:**
- xxx.xxx.xx.controller:存放Servlet程序的包
- xxx.xxx.xx.controller.user
- xxx.xxx.xx.controller.product
- xxx.xxx.xx.controller.order
- xxx.xxx.xx.controller.cart
- xxx.xxx.xx.dao:存放dao接口的包
- xxx.xxx.xx.dao.impl:存放dao接口实现类的包
- xxx.xxx.xx.entity:存放实体类的包
- xxx.xxx.xx.util:存放项目相关工作类的包
- ...
**web目录结构如下:**
- css
- js
- images
- ...
### 2、数据库
```mssql
-- 创建数据库
create database productdb ;
-- 进入数据库
use productdb ;
-- 创建数据表
create table product
(
product_id varchar(50) primary key not null , --产品ID
product_name varchar(50) not null , --产品名称
product_price float not null , --产品库存
product_count int not null default 1 , --产品类型
product_desc varchar(200) default '暂无描述' --描述
)
-- 添加测试数据
-- insert into 表名(字段1,字段2,...) values (值1,值2,...)
-- 查询数据
select * from product ;
-- 删除数据
-- delete from 表名 where 条件
-- 修改数据
--update 表名 set 字段1=值,字段2=值,... where 条件
-- 查询数据
--select 字段1,字段2,.. from 表名 where 条件
create table users
(
id int auto_increment primary key ,
username varchar(50) unique not null ,
`password` char(32) not null ,
`level` varchar(10) default '会员' ,
`image` varchar(50) default 'default.jpg',
phone varchar(20) ,
register_time datetime default now(),
`status` int default 1
)
insert into users(username,`password`,phone) values ('zing','123456','13417747371')
```
### 3、具体操作步骤
#### 1)编写DBUtil
xxx.xxx.xx.util
中文乱码过滤器
#### 2)编写实体对象
xxx.xxx.xx.entity
每一张数据表一般都有一个与之对应的对象,我们称之为实体对象。
作用:实现数据的封装和传递.
一般类名与表名相同或具有相同的意义的名称
* 类中的属性与数据表的字段一一对应的关系
* 属性一般要私有化
* 同时,生成setter/getter方法
* 根据实际情况,可以重写构造方法
#### 3)编写DAO
**服务于Servlet**
DAO:Data Access Object,数据访问对象
主要职责:实现数据表的CRUD操作
1. 定义一个操作数据表的接口(标准),在此接口中,定义相关的操作 -- 学生 - 学习
2. 实现接口(根据业务或需求,可以给接口指定多种实现)-- 学生 -- 具体学习的实现
**第一:定义DAO接口**
```java
public interface ProductDao {
// 抽象方法 (5个)
public void add(Product p) ;
//...
}
```
**第二:定义DAO接口实现**
```java
public interface ProductDao implements ProductDAO{
public void add(Product p) {
//第一:定义操作数据库的SQL语句
//第二:获取连接对象
//第三:使用连接对象,获取语句对象(PreparedStatement),并预编译SQL语句
//第四:设置数据
//语法:语句对象.setXxx(问号占位符索引,数据) ;
//第五:执行SQL语句,并接收返回结果
//语句对象.executeUpdate() -> 增,删,改 -> 返回的是受影响的记录数
//语句对象.executeQuery() -> 查 -> 返回的是结果集(ResultSet)
//第六:对结果进行处理
//遍历结构集各行各列的数据,封装到相关的实体对象或集合
//判断有没有数据:结果集对象.next()
//获取结果集的数据:
//结果集对象.getXxx(查询数据的索引) ;
//结果集对象.getXxx(查询数据的字段名称) ;
//第七:关闭对象
//MyBatis
}
//...
}
```
说明
- 如果查询到一个结果,则一般会封装为一个实体对象,并返回 -> if
- 如果查询到多个结果,则一般会封装为一个集合对象(List),并返回 -> while
#### 4)编写页面
HTML、JSP、CSS、JS、EL、JSTL、Vue、BootStrap、LayUI、Vant(移动端)
#### 5)编写Servlet - 核心的控制器
第一:定义一个类
第二:继承HttpServlet
第三:重写service方法
第四:在service方法中,编写代码,实现业务逻辑功能
第五:使用@WebServlet,设置访问URL
#### 6)发布项目、运行测试
http://localhost:8080/项目名称/xxx.html
http://localhost:8080/项目名称/xxx.jsp
http://localhost:8080/项目名称/servlet
## 二、Git搭建
### 1、基本操作
1)组长在本机创建初始项目(IDE工具 + 忽略文件),同时,创建本地仓库并提交第一个版本
2)创建空的远程仓库,并创建和设置相关的分支
2.1)创建相关的分支
- 主分支
- 开发分支
- 功能分支(以组员命名的分支)
2.2)添加开发成员
2.3)设置分支规则
3)添加远程仓库,推送本地仓库到远程
4)切换成员对应的分支,实现相关模块的功能
- 拉取
- 推送
- Pull Requests,请并合并到开发分支
- 拉取、合并master分支,更新其它成员的代码到自己的分支中(反复工作)
5)pull Requests,请求开发分支,合并到master分支中
6)打标签
### 2、接口文档
>API01.md
>
>API02.md
>
>...
1)接口功能:
查询所有商品数据
2)URL地址(示例)
http://localhost:8080/pms/products
3)支持格式
JSON
4)HTTP请求方式
GET
5)请求参数(根据需求给出参数或者不给参数)
| 字段名 | 字段类型 | 是否必须 | 长度 |
| ----------- | -------- | -------- | ---- |
| productName | String | false | 50 |
| price | double | false | |
6)返回字段(ResultDto封装了三个字段:code、msg、value)
| 返回字段 | 字段类型 | 说明 |
| -------- | -------- | ------------------------------------------------------ |
| code | Int | 返回状态:200或500 |
| msg | String | 成功标志:success |
| result | List | 查询到所有表中数据的封装被封装到List中,以json形式返回 |
7)请求示例(JSON类型)
```js
"code": 200,
"msg": "登录成功",
"result": {username:xxx,sex:xx,age:xx}
"code": 500,
"msg": "错误的帐号或密码",
"result":''
```
## 三、登录验证
在WEB项目中,当我们使用前后端分离实现时,我们会遇到两个问题,分别是
- 跨域问题
- 保存用户信息的session和cookie问题
### 1、自定义过滤器,解决跨域问题
#### 1)后端过滤器的定义
```java
/**
* 编写过滤器,实现跨域访问
*
* @author Administrator
*
*/
//第一:定义一个类,实现Filter接口
@WebFilter(urlPatterns = "/api/*")
public class CORSFilter implements Filter {
//第二:重写Filter接口中的方法
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("跨域处理... ");
}
@Override
public void doFilter(ServletRequest sRequest, ServletResponse sResponse, FilterChain chain)
throws IOException, ServletException {
// 第三:实现过滤功能
//1.向下转型
HttpServletRequest request = (HttpServletRequest)sRequest ;
HttpServletResponse response = (HttpServletResponse)sResponse ;
//2.设置相关的头信息
// 允许所有的域名
response.setHeader("Access-Control-Allow-Origin", "*");
// 允许发送cookies
response.setHeader("Access-Control-Allow-Credentials", "true");
// 允许请求所有的方法
response.setHeader("Access-Control-Allow-Methods", "DELETE,PUT,POST,GET");
// 预检请求的最大超时(有效)时间为3600秒
response.setHeader("Access-Control-Max-Age", "3600");
// 定义可以返回的头部信息字段
response.setHeader("Access-Control-Allow-Headers", "Authorization,Origin,X-Requested-With,Content-Type,Accept,"
+ "content-Type,origin,x-requested-with,content-type,accept,authorization,token,id,X-Custom-Header,X-Cookie,Connection,User-Agent,Cookie,*");
response.setHeader("Access-Control-Request-Headers", "Authorization,Origin, X-Requested-With,content-Type,Accept");
// 可以暴露给外部所有头部信息字段
response.setHeader("Access-Control-Expose-Headers", "*");
//3.放行
chain.doFilter(request, response);
}
@Override
public void destroy() {
System.out.println("跨域处理-销毁");
}
}
```
#### 2)前端发起Ajax请求
在实际的开发中,对于前后端分离的项目,每次ajax请求无法携带cookie到后端,导致每次都会创建一个session,而无法获取login时保存的信息。而需要在前端的Ajax请求中,使用以下两个参数:
- crossDomain:true:解决跨域配置
- xhrFields: {withCredentials:true}:发送Ajax时,Request header中便会带上 Cookie 信息
对应客户端的 `xhrFields.withCredentials: true` 参数,服务器端通过在响应 header 中设置 **`Access-Control-Allow-Credentials = true`** 来运行客户端携带证书式访问。通过对 Credentials 参数的设置,就可以保持跨域 Ajax 时的 Cookie。这里需要注意的是:**服务器端 `Access-Control-Allow-Credentials = true`时,参数`Access-Control-Allow-Origin` 的值不能为 `'\*'`** 。
```js
$("#login-btn").click(function () {
$.ajax({
url: 'http://localhost:8080/pms/api/login.do',
type: 'post',
crossDomain:true,
xhrFields: {
withCredentials:true
},
data: $("#login-form").serialize(),
dataType: 'json',
success: function (responseData) {
if (responseData.code == 200) {
// 把当前登录的帐号保存到会话存储中
sessionStorage.setItem("user", JSON.stringify(responseData.value));
window.location.href = "index.html";
} else {
alert(responseData.msg);
}
}
});
});
```
在后端的Filter配置中,需要把Access-Control-Allow-Credentials设置为true,表示允许跨域请求携带cookie;同时,还要把Access-Control-Allow-Origin设置为“*”,表示允许跨域访问。但如果在前端的Ajax请求中,使用参数crossDomain和xhrFields参数时,则会报以下错误:
```
Access to XMLHttpRequest at 'http://localhost:8080/pms/api/login.do' from origin 'http://127.0.0.1:5500' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
```
原因:这样设置和前端跨域请求携带cookie产生了冲突,这时把“*” 换作前端项目的部署服务器的ip即可,如:
```java
// response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1:5500");
// 允许发送cookies
response.setHeader("Access-Control-Allow-Credentials", "true");
// 允许请求所有的方法
response.setHeader("Access-Control-Allow-Methods", "DELETE,PUT,POST,GET");
```
### 2、使用Tomcat的CorsFilter
另外,也可以使用apache的提供的过滤器组件(tomcat-catalina)来解决跨域问题。
> http://tomcat.apache.org/tomcat-9.0-doc/config/filter.html#CORS_Filter
第一:项目中,引入tomcat-catalina.jar
第二:配置过滤器
```xml
CorsFilter
org.apache.catalina.filters.CorsFilter
CorsFilter
/*
```
第三:参数配置
以上的过滤器的配置是最少配置,很多参数都进行了默认的配置,根据需求,进行相关的配置,如:
```xml
CorsFilter
org.apache.catalina.filters.CorsFilter
cors.allowed.origins
*
cors.allowed.methods
GET,POST,HEAD,OPTIONS,PUT
cors.allowed.headers
Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers
cors.exposed.headers
Access-Control-Allow-Origin,Access-Control-Allow-Credentials
cors.support.credentials
true
cors.preflight.maxage
10
CorsFilter
/*
```
### 3、踩坑
在chrome浏览器某个版本开始,默认不携带cookie到服务器中。解决办法:
1)在浏览地址栏访问:`chrome://flags `
2)搜索SameSite,设置为disabled

3)重启浏览器
### 4、代码片段
#### 1)login.html
```html
```
#### 2)login.js
```js
```
#### 3)登录Servlet
```java
// 第一:获取页面发送过来的商品信息(合法性验证[略])
String username = req.getParameter("username") ;
String password = req.getParameter("password") ;
// 第二:创建实体对象,并封装数据
// 第三:创建DAO对象,实现数据添加操作
UserDao userDao = DaoFactory.getUserDaoInstance() ;
User user = userDao.selectUser(username);
// 第四:响应结果
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter() ;
if(user!=null && password!=null && password.equals(user.getPassword())) {
out.print(new Gson().toJson(successJson(user,200,"登录成功")));
} else {
out.print(new Gson().toJson(errorJson("错误的帐号或密码")));
}
// 第五:关闭对象
out.flush();
out.close();
```
#### 4)后台主页——index.html
```html
```
#### 5)后台主页——index.js
```js
// 在会话存储中,获取登录用户信息
let loginUser = JSON.parse(sessionStorage.getItem("user")) ;
this.user = loginUser ;
if(this.user==null) {
location.href = "login.html" ;
}
```
**登录验证封装**
```js
function login() {
let loginUser = JSON.parse(sessionStorage.getItem("user"));
if (loginUser == null) {
location.href = "login.html";
}
return loginUser ;
}
```
#### 6)退出登录
```js
logout:function() {
// sessionStorage.removeItem("user")
sessionStorage.clear() ;
window.location.replace("login.html") ;
}
```
## 四、传值
### 1、JS获取URL请求参数
```js
function getQueryString(name) {
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
var r = window.location.search.substr(1).match(reg);
if (r != null) {
return unescape(r[2]);
}
return null;
}
```
### 2、本地存储实现
>参考资料:https://www.w3school.com.cn/html/html5_webstorage.asp
1)保存数据到本地
```js
sessionStorage.setItem('key', 'value') ;
localStorage.setItem('key', 'value') ;
// 如果值是对象,则通过“JSON.stringify"转换
```
2)获取本地存储数据
```js
localStorage.getItem('key') ;
sessionStorage.getItem('key') ;
// 如果获取的是对象,则通过“JSON.parse"转换
```
3)删除本地存储的某个数据
```js
localStorage.removeItem('key');
sessionStorage.removeItem('key');
```
4)清空本地存储的所有数据
```js
localStorage.clear();
sessionStorage.clear();
```
5)监听本地存储的变化
> Storage 发生变化(增加、更新、删除)时的 触发,同一个页面发生的改变不会触发,只会监听同一域名下其他页面改变 Storage。
>
> https://www.cnblogs.com/ranyonsue/p/11429991.html
```js
window.addEventListener('storage', function (e) {
console.log('key', e.key);
console.log('oldValue', e.oldValue);
console.log('newValue', e.newValue);
console.log('url', e.url);
}) ;
```
其中,e 就是一个 StorageEvent 对象,常见的属性有:
- key:设置或删除或修改的键。调用clear()时,则为null。
- oldValue:改变之前的旧值。如果是新增元素,则为null。
- newValue:改变之后的新值。如果是删除元素,则为null。
- storageArea:该属性是一个引用,指向发生变化的sessionStorage或localStorage对象
- url:触发这个改变事件的页面的URL
## 五、多表查询
> 注意:数据表与数据表之间可能存在主外键关联,那么根据业务需要,可能会进行多表的查询,如何实现呢?
### 1、数据表
```mssql
create database myweb ;
use myweb ;
--用户信息表
create table users
(
id int identity(1,1) primary key not null , --用户ID
username varchar(50) unique not null , --用户名
[password] varchar(50) not null , --密码
[name] varchar(50) default null , --真实姓名
[level] varchar(10) default '会员' , --等级
[image] varchar(50) default 'default.jpg', --头像
sex char(2) default '男' , --性别
phone varchar(15) not null , --电话
[address] varchar(200) not null , --地址
zipCode varchar(8) , --邮编
email varchar(50) not null , --邮箱
register_time datetime default getdate(), --用户注册时间
[status] int default 1 --状态
);
create table users(
id int auto_increment primary key, -- 用户ID
username varchar(50) unique not null , -- 用户名
`password` varchar(50) not null , -- 密码
`name` varchar(50) default null , -- 真实姓名
`level` varchar(10) default '会员' , -- 等级
`image` varchar(50) default 'default.jpg', -- 头像
sex char(2) default '男' , -- 性别
phone varchar(15) not null , -- 电话
`address` varchar(200) not null , -- 地址
zipCode varchar(8) , -- 邮编
email varchar(50) not null , -- 邮箱
register_time datetime default now(), -- 用户注册时间
`status` int default 1 -- 状态
);
insert into users(username,`password`,name,`level`,sex,phone,address,zipCode,email)
values ('san','123456','张三三','会员','男','110','广东珠海','519000','sansan@qq.com');
insert into users(username,`password`,name,`level`,sex,phone,address,zipCode,email)
values ('si','123456','李四四','会员','女','120','广东广州','519000','sisi@qq.com');
insert into users(username,`password`,name,`level`,sex,phone,address,zipCode,email)
values ('wu','123456','王五五','会员','女','130','广东深圳','519000','wuwu@qq.com');
insert into users(username,`password`,name,`level`,sex,phone,address,zipCode,email)
values ('liu','123456','赵六六','管理员','女','140','广东中山','519000','liuliu@qq.com');
insert into users(username,`password`,name,`level`,sex,phone,address,zipCode,email)
values ('qi','123456','钱七七','管理员','男','150','广东佛山','519000','qiqi@qq.com');
select id,username,password,name,level,image,sex,phone,address,zipCode,email,register_time,status from users;
-- 留言信息 1
create table message
(
-- 留言ID
id int identity(1,1) primary key not null ,
title varchar(20) not null ,
content text ,
author_id int foreign key references users(id) ,
message_time datetime default getdate()
);
-- 回复信息 M
create table replay
(
-- 回复ID
id int identity(1,1) primary key not null ,
title varchar(20) not null ,
content text ,
message_time datetime default getdate() ,
-- 外键
message_id int foreign key references message(id) ,
author_id int foreign key references users(id)
);
--分页查询SQL语句
select * from (
select *, ROW_NUMBER() OVER(Order by a.字段 asc) AS RowNumber from 表名 as a
) as b
where RowNumber BETWEEN (当前页数-1)*每页记录数+1 and 当前页数 * 每页记录数
```
### 2、实现一
1.根据业务,编写多表查询的SQL语句,并执行
2.编写值对象(VO),封装多表查询的结果
### 3、实现二
- 对象(类)与对象(类)之间的关联关系(映射关系)
- 1对1:在一个对象中,定义另一个对象
- 1对多:在一个对象中,定义一个集合或数组
- 多对多
- 根据对象之间的彼此关联又分为:
- 单向关联
- 双向关联
```java
// 班级对象
public class BanJi {
//班级:学生 -- 1 对 多
private List students ;
}
// 学生对象
public class Student {
//学生 : 班级 -- 多 对 1
private BanJi banJi ;
}
```
## 六、多条件查询
```java
public List queryUser(User user) {
List list = new ArrayList() ;
// 第一:定义操作数据库的SQL语句,动态添加条件
StringBuilder sql =new StringBuilder("SELECT ID,USERNAME,PASSWORD,NAME,LEVEL,IMAGE,SEX,PHONE,ADDRESS,ZIPCODE,EMAIL,REGISTER_TIME,STATUS FROM USERS WHERE 1=1");
// 1.定义集合,用于存储SQL语句的参数值
List