# 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 ![image-20210103154015231](https://tva1.sinaimg.cn/large/0081Kckwgy1gmajsgmbe1j31n60m4djv.jpg) 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 params = new ArrayList(); // 2.拼接SQL语句 if(user.getUsername()!=null) { sql.append(" AND USERNAME LIKE ?") ; params.add("%" + user.getUsername() + "%") ; } if(user.getLevel()!=null) { sql.append(" AND `LEVEL` = ?") ; params.add(user.getLevel()) ; } if(user.getPhone()!=null) { sql.append(" AND PHONE = ?") ; params.add(user.getPhone()) ; } //第二:获取连接对象 Connection conn = DB4MySQLUtil.getConnection(); //第三:使用连接对象,获取语句对象(PreparedStatement),并预编译SQL语句 PreparedStatement pstmt = null ; ResultSet rst = null ; try { pstmt= conn.prepareStatement(sql.toString()); //第四:设置数据 //语法:语句对象.setXxx(问号占位符索引,数据) ; //遍历集合,填充参数值 for(int i=0;i 增,删,改 -> 返回的是受影响的记录数 //语句对象.executeQuery() -> 查 -> 返回的是结果集(ResultSet) rst = pstmt.executeQuery() ; //第六:对结果进行处理 //遍历结构集各行各列的数据,封装到相关的实体对象或集合 //判断有没有数据:结果集对象.next() //获取结果集的数据: //结果集对象.getXxx(查询数据的索引) ; //结果集对象.getXxx(查询数据的字段名称) ; while(rst.next()) { // 1.读取数据 int id = rst.getInt(1) ; String username = rst.getString(2) ; String password = rst.getString(3) ; String name = rst.getString(4) ; String level = rst.getString(5) ; String image = rst.getString(6) ; String sex = rst.getString(7) ; String phone = rst.getString(8) ; String address = rst.getString(9) ; String zipCode = rst.getString(10) ; String email = rst.getString(11) ; Date regTime = rst.getTimestamp(12) ; int status = rst.getInt(13) ; // 2.创建实体对象 User u = new User() ; //3.封装数据 u.setId(id); u.setUsername(username); u.setPassword(password); u.setName(name); u.setLevel(level); u.setImage(image); u.setSex(sex); u.setPhone(phone); u.setAddress(address); u.setZipCode(zipCode); u.setEmail(email); u.setRegisterTime(regTime); u.setStatus(status); list.add(u) ; } } catch (SQLException e) { e.printStackTrace(); } finally { // 第七:关闭对象 DB4MySQLUtil.close(conn,pstmt,null); } return list; } ``` ## 七、WebSocket ### 1、概述 WebSocket是HTML5中强大的通信功能,它定义了一个全双工通信信道。扩展了浏览器与服务端的通信功能(**实时通信**),使服务端也能主动向客户端发送数据。 在WebSocket之前,传统的实现方法有: 1. Ajax轮询(polling); 2. Comet技术:长轮询(long-polling)、流(streaming) 轮询是最原始的实现实时Web应用的解决方案。轮询技术要求客户端以设定的时间间隔周期性地向服务端发送请求,频繁地查询是否有新的数据改动。明显地,这种方法会导致过多不必要的请求,浪费流量和服务器资源。 Comet技术又可以分为长轮询和流技术。长轮询改进了上述的轮询技术,减小了无用的请求。它会为某些数据设定过期时间,当数据过期后才会向服务端发送请求;这种机制适合数据的改动不是特别频繁的情况。流技术通常是指客户端使用一个隐藏的窗口与服务端建立一个HTTP长连接,服务端会不断更新连接状态以保持HTTP长连接存活;这样的话,服务端就可以通过这条长连接主动将数据发送给客户端;流技术在大并发环境下,可能会考验到服务端的性能 这两种技术都是基于请求-应答模式,都不算是真正意义上的实时技术;它们的每一次请求、应答,都浪费了一定流量在相同的头部信息上,并且开发复杂度也较大。 JavaEE 7中出了JSR-356:Java API for WebSocket规范。不少Web容器,如Tomcat、Nginx、Jetty等都支持WebSocket。Tomcat从7.0.27开始支持 WebSocket,从7.0.47开始支持JSR-356。 ### 2、执行过程 WebSocket的执行过程为: 1) 客户端向服务器发送数据 2) 服务器获取客户端的数据 3) 服务器向客户端发送数据 4) 客户端向服务器端获取数据 ### 3、实现步骤 #### 第一:创建WebSocket服务器端程序(JAVA) > 在类的上面使用注解:@ServerEndpoint("/URL") > > 客户端可以通过这个URL来连接到WebSocket。类似Servlet的注解url-pattern映射。 ##### 1、定义一个类 ```java @ServerEndpoint("/hello") public class HelloSocket { ... } ``` ##### 2、定义相关的事件方法 ```java /** * 成功建立连接时,调用的方法 * @param session 与某个客户端的连接会话,需要通过它来给客户端发送数据 */ @OnOpen public void open(Session session) { } /** * 连接关闭时,调用的方法 * @param session 会话 */ @OnClose public void close(Session session) { } /** * 收到客户端消息后,调用的方法 * @param session 会话 * @param msg 客户端发送的消息 */ @OnMessage public void echoMessage(Session session, String msg) { } ``` ##### 3、服务器获取客户端的数据 ```java @OnMessage public void echoMessage(Session session, String msg) { // msg参数,就是客户端发送来的数据 } ``` ##### 4、服务器向客户端发送数据 >会话对象.getBasicRemote().sendText("消息"); ```java @OnMessage public void echoMessage(Session session, String msg) { try { // 服务器向客户端发送数据 session.getBasicRemote().sendText("你好!"); } catch (IOException e) { e.printStackTrace(); } } ``` ##### 5、完整代码 ```java /** * 创建WebSocket服务器的端点程序(Endpoint) * @author zqx * @date 2020-12-28 */ @ServerEndpoint("/hello") public class HelloSocket { /** * 成功建立连接时,调用的方法 * @param session 与某个客户端的连接会话,需要通过它来给客户端发送数据 */ @OnOpen public void open(Session session) { System.out.println("建立连接,您的会话ID为:" + session.getId()); } /** * 连接关闭时,调用的方法 * @param session 会话 */ @OnClose public void close(Session session) { System.out.println("会话结束,当前结束的会话ID为:"+session.getId()+" is closed ..."); } /** * 收到客户端消息后,调用的方法 * @param session 可选参数,根据需要选择使用 * @param msg 客户端发送的消息 */ @OnMessage public void echoMessage(Session session, String msg) { // 服务器获取客户端的数据:msg System.out.println("client:"+msg); try { // 服务器向客户端发送数据 session.getBasicRemote().sendText(msg + ",你好!"); } catch (IOException e) { e.printStackTrace(); } } } ``` **说明:** 1)服务器的端点程序定义相关的事件注解,触发到相关的事件时,调用注解下的方法: - @OnOpen:打开连接时,触发的响应事件 - @OnMessage:收到客户端消息时,触发的响应事件 - @OnClose:关闭连接时,触发的响应事件 - @OnError:发生错误时,触发的响应事件 2)Session指的是当前客户端与服务器端连接通信管道的会话(有多少个连接,就有多少个session) 3)每个管道都是独立的Endpoint对象 #### 第二:定义WebSocket的核心配置类 ```java public class DemoConfig implements ServerApplicationConfig { /** * 1.注解方式的启动 * getAnnotatedEndpointClasses:扫描src下面所有的类,并把具有@ServerEndPoint注解的类添加到set集合中 * 具有@ServerEndPoint注解的类就是一个Endpoint,它是一个Websocke的一个服务器程序 */ @Override public Set> getAnnotatedEndpointClasses(Set> set) { System.out.println("ServerApplicationConfig启动了...."); System.out.println("Endpoint的数量:"+set.size()); // 在启动服务器时,会扫描所有的Endpoint,并添加到set集合中,并返回; // 同时,在返回之前可以对set集合进行处理(过滤) return set; } // 2.接口方式的启动 public Set getEndpointConfigs(Set> arg0) { return null; } } ``` 说明: 1)实现ServerApplicationConfig接口的类,会在项目启动时自动执行(与ContextListener相似),在启动服务器时,会扫描所有的Endpoint,并添加到set集合中,并返回。 2)启动方式有两种方法,分别为: - 注解方式的启动 - 接口方式的启动 在定义时,使用其中一种方法即可。 #### 第三:创建WebSocket客户端程序(JS) ##### 1、创建WebSocket对象,并指定服务器端程序 ```js let ws = null; // 定义服务器端程序连接地址 let target = "ws://localhost:8080/product-sys/hello"; // 浏览器兼容判断 if ('WebSocket' in window) { ws = new WebSocket(target); } else if ('MozWebSocket' in window) { ws = new MozWebSocket(target); } else { alert('对不起,您的浏览器不支持WebSocket,请更新或更换浏览器.'); } ``` ##### 2、客户端向服务器发送数据 >客户端向服务器发送数据:WebSocket对象.send(消息) ; ```js ws.send('好好学习,天天向上') ; ``` ##### 3、客户端向服务器端获取数据 > 获取服务器端返回的数据:事件对象.data属性 ```js // 接收到消息的回调方法 ws.onmessage = function (event) { // console.log(event) ; // document.getElementById("result").innerHTML += event.data +"
"; console.log(event.data) ; }; ``` 说明:在前端的WebSocket对象中,也定义了相关的事件对象,作用也服务器端的相似,分别为: - onopen:打开连接时,触发的响应事件 - onmessage:收到服务器消息时,触发的响应事件 - onclose:关闭连接时,触发的响应事件 - onerror:发生错误时,触发的响应事件 ```js // 收到服务器消息时,触发此事 ws.onopen = function (event) { // ... }; // 收到服务器消息时,触发的响应事件 ws.onmessage = function (event) { // .. }; // 关闭连接时,触发的响应事件 ws.onopen = function (event) { // ... }; // 发生错误时,触发的响应事件 ws.onerror = function (event) { // ... }; //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。 window.onbeforeunload = function(){ websocket.close(); } ``` ##### 4、完整代码 ```js let ws = null; // 1.创建WebSocket对象,并指定服务器端程序(封装方法) function connect() { // 1)定义服务器端程序连接地址 let target = "ws://localhost:8080/demo01/hello"; // 2)浏览器兼容判断 if ('WebSocket' in window) { ws = new WebSocket(target); } else if ('MozWebSocket' in window) { ws = new MozWebSocket(target); } else { alert('WebSocket is not supported by this browser.'); return; } } // 调用 connect(); // 2.客户端向服务器发送数据:WebSocket对象.send(消息) ; function sendMessage() { let msgTxt = document.getElementById("msg") ; if(ws!=null) { // 发送消息 ws.send(msgTxt.value) ; msgTxt.value = "" ; msgTxt.focus() ; } else { alert("WebSocket未连接,请连接") ; } } // 3.客户端接收服务器端的数据 ws.onmessage = function (event) { // console.log(event) ; // document.getElementById("result").innerHTML += event.data +"
"; console.log(event.data) ; }; ``` ## 八、在线编辑 ### 1、官网 http://fex.baidu.com/ueditor/#api-common #### 2、下载 https://github.com/fex-team/ueditor/releases #### 3、使用 ```html ueditor demo ``` #### 4、配置 ##### 1)基本配置 http://fex.baidu.com/ueditor/#start-config ```js var ue = UE.getEditor('container', { toolbars: [ ['fullscreen', 'source', 'undo', 'redo', 'bold'] ], autoHeightEnabled: true, autoFloatEnabled: true }); ``` ##### 2)文件上传 第一:配置正确的统一请求接口路径 ```js // 打开ueditor.config.js文件,大概在32行 // 服务器统一请求接口路径 , serverUrl: URL + "jsp/controller.jsp" ``` 第二:设置访问文件的URL前缀 ```js // 打开config.json文件,分别配置访问图片和文件的URL前缀 // 图片访问路径前缀,其实就是指定项目名(项目上下文件路径) // 大概在11行 "imageUrlPrefix": "/product-sys", // 文件访问路径前缀(原理同上) // 大概在63行 "fileUrlPrefix": "/product-sys", ``` 第三:配置文件上传的位置 ```js // 上传保存路径,可以自定义保存路径和文件名格式 "imagePathFormat": "/ueditor/jsp/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}", // 上传保存路径,可以自定义保存路径和文件名格式 "filePathFormat": "/ueditor/jsp/upload/file/{yyyy}{mm}{dd}/{time}{rand:6}", ``` #### 5、表单提交 ```html ueditor demo
``` ## 九、地图定位 ## 十、在线支付 ### 1、当面付 #### 1)下载当面付Demo https://opendocs.alipay.com/open/194/105201 #### 2)在IDEA中导入Demo项目 ![image-20200815231603352](https://tva1.sinaimg.cn/large/007S8ZIlgy1ghrwl6x5b3j30sn0f1diy.jpg) #### 3)配置 打开项目中`zfbinfo.properties`,进行相关的配置,详细的配置参考`沙箱应用` https://openhome.alipay.com/platform/appDaily.htm ![image-20200815231426830](https://tva1.sinaimg.cn/large/007S8ZIlgy1ghrwjiq7lvj31in0u045x.jpg) **RSA2(SHA256)密钥`设置/查看`** ![image-20200815231852917](https://tva1.sinaimg.cn/large/007S8ZIlgy1ghrwo4xevfj31780s616w.jpg) **详细配置如下:** ```properties # 支付宝网关名、partnerId和appId # 配置一:支付宝网关 open_api_domain = https://openapi.alipaydev.com/gateway.do mcloud_api_domain = http://mcloudmonitor.com/gateway.do # 配置二:商户UID pid = 略 # 配置三:APPID appid = 略 # RSA私钥、公钥和支付宝公钥 # 配置四:应用私钥,参考https://opendocs.alipay.com/open/291/105971 private_key = 略 #配置五:应用公钥 public_key = 略 #SHA1withRsa对应支付宝公钥 #alipay_public_key = MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDI6d306Q8fIfCOaTXyiUeJHkrIvYISRcc73s3vF1ZT7XN8RNPwJxo8pWaJMmvyTn9N4HQ632qJBVHf8sxHi/fEsraprwCtzvzQETrNRwVxLO5jVmRGi60j8Ue1efIlzPXV9je9mkjzOmdssymZkh2QhUrCmZYI/FCEa3/cNMW0QIDAQAB #SHA256withRsa对应支付宝公钥 # 配置六:支付宝公钥: alipay_public_key = 略 # 签名类型: RSA->SHA1withRsa,RSA2->SHA256withRsa sign_type = RSA2 # 当面付最大查询次数和查询间隔(毫秒) max_query_retry = 5 query_duration = 5000 # 当面付最大撤销次数和撤销间隔(毫秒) max_cancel_retry = 3 cancel_duration = 2000 # 交易保障线程第一次调度延迟和调度间隔(秒) heartbeat_delay = 5 heartbeat_duration = 900 ``` 生成密钥,请参考 https://opendocs.alipay.com/open/291/105971 #### 4)运行程序 运行Demo中的Main.java,运行结果如下: ```json body: { "alipay_trade_precreate_response": { "code": "10000", "msg": "Success", "out_trade_no": "tradeprecreate15975051909168861027", "qr_code": "https:\/\/qr.alipay.com\/bax03023q2jbsynkoy0g0013" }, "sign": "MVUnFFuqQht/Ctspw8HFf3FLWz1UoROas...略" } ``` 打开网站`https://cli.im/`,复制`qr_code`属性值生成相应的二维码,使用沙箱帐号扫描支付。 5)部署web应用 访问:http://localhost:8080/tradepay/index.html **选择操作菜单** ![image-20200815233244708](https://tva1.sinaimg.cn/large/007S8ZIlgy1ghrx2k39wmj31rq0f4n00.jpg) **输入确认信息** ![image-20200815233422336](https://tva1.sinaimg.cn/large/007S8ZIlgy1ghrx48vw7aj31a80gon0a.jpg) **登录沙箱帐号,扫描二维码支付** ![image-20200815233519944](https://tva1.sinaimg.cn/large/007S8ZIlgy1ghrx598i9hj324i0gytbj.jpg) ### 2、项目整合当面付 #### 1)拷贝jar包到项目中 ![image-20200816145129657](https://tva1.sinaimg.cn/large/007S8ZIlgy1ghsnmun23pj30o40owju4.jpg) 其中,我们可以只拷贝粉色框的四个jar包到项目中,其它的jar包通过Maven依赖。但需要在pom.xml中配置以下插件,把本地项目lib目录下的jar文件一并打包。 ```xml mmall org.apache.maven.plugins maven-compiler-plugin 1.7 1.7 UTF-8 ${project.basedir}/src/main/webapp/WEB-INF/lib ... ``` 其它jar包的Maven依赖(建议保持版本与Demo的一致) ```xml ... commons-codec commons-codec 1.10 commons-configuration commons-configuration 1.10 commons-lang commons-lang 2.6 commons-logging commons-logging 1.1.1 com.google.zxing core 2.1 com.google.code.gson gson 2.3.1 org.hamcrest hamcrest-core 1.3 ... ``` #### 2)添加依赖 把手动导入的四个jar包,添加到项目依赖中 ![image-20200816150637192](https://tva1.sinaimg.cn/large/007S8ZIlgy1ghso28y2k2j310t0u0kdh.jpg) #### 3)测试 把Demo的两个程序,拷贝到项目中,并运行Main.java测试是否整合成功。 ![image-20200816151123976](https://tva1.sinaimg.cn/large/007S8ZIlgy1ghso77xe8tj31nr0u07go.jpg) 第一:登录www.alipay.cm网站 第二:设置沙箱环境 1.生成密钥 2.充值 第三:下载沙箱环境的支付宝APP 第四:下载Demo,导入到Eclipse中 第五:配置相关参数 1、AppId 2、网关 3、私钥、公钥 第六:启动项目,测试 第七:应用到你自己的项目中