因为上一篇内容太多,导致打字卡顿,所以决定分开。
安全退出功能分析需求与设计
@RequestMapping("/settings/qx/user/logout.do")
public String logout(HttpServletResponse response, HttpSession session) {
Cookie c1 = new Cookie("loginAct", "1");
c1.setMaxAge(0);
response.addCookie(c1);
Cookie c2 = new Cookie("loginPwd", "2");
c2.setMaxAge(0);
response.addCookie(c2);
session.invalidate();
// 重定向到首页
return "redirect:/";
}
为什么使用重定向来跳转:
因为,当你使用重定向时,浏览器中所显示的URL会变成新页面的URL, 而当使用转发时,该URL会保持不变。
index.jsp
添加退出登录登录
$("#logoutBtn").click(function() {
window.location.href = "/settings/qx/user/logout.do"
})
好了,我们来测试一下。
点击退出后
出现了状态404
后来发现地址开头不能有斜杠/
把斜杠/
去掉即可
重新测试,成功安全退出
十.登录验证和创建市场活动
1,登录验证:
1)过滤器:
a)implements Filter{
–init
–doFilter
–destroy
}
b)配置过滤器:web.xml
2)拦截器:
a)提供拦截器类:implements HandlerInterceptor{
–pre
–post
–after
}
b)配置拦截器:springmvc.xml
2,页面切割技术:
1)和:
:用来切割页面.
:显示页面.
<frameset cols="20%,60%,20%">
<frame src="url1" name="f1">
<frame src="url2" name="f2">
<frame src="url3" name="f3">
</frameset>
每一个<frame>标签就是一个独立的浏览器窗口。
<a href="url" target="f3">test</a>
2)
模态窗口:模拟的窗口,本质上是
初始时,z-index初始参数是<0,所以不显示;
需要显示时,z-index值设置成>0即可。
bootstrap来控制z-index的大小。
控制模态窗口的显示与隐藏:
1)方式一:通过标签的属性data-toggle=“modal” data-target=“模态窗口的id”
2)方式二:通过js函数控制:
选择器(选中div).modal(“show”);//显示选中的模态窗口
选择器(选中div).modal(“hide”);//关闭选中的模态窗口
3)方式三:通过标签的属性data-dismiss=""
点击添加了data-dismiss="“属性的标签,自动关闭该标签所在的模态窗口。
模态窗口的意义:
window.open(“url”,”_blank");
模态窗口本质上就是原来页面中的一个
这里我调整一下目录结构,太乱了。
创建登录拦截器LoginInterceptor
package com.example.crm.settings.web.interceptor;
import com.example.crm.commons.constants.Constants;
import com.example.crm.settings.domain.User;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* @Author 沧海轻舟
* @Date 2022/3/11 19:14
* @Description 登陆拦截器
*/
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
//如果用户没有登录成功,则跳转到登录页面
HttpSession session=httpServletRequest.getSession();
User user=(User) session.getAttribute(Constants.SESSION_USER);
if(user==null){
httpServletResponse.sendRedirect(httpServletRequest.getContextPath());//重定向时,url必须加项目的名称
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
为啥重定向时,url必须加项目的名称,而这里却没有,只加了个斜杠(因为借用springmvc
来重定向,会自动翻译成response.sendRedirect("/crm")
)
在配置文件applicationContext-mvc.xml
配置拦截器
<mvc:interceptors>
<mvc:interceptor>
<!--配置拦截的请求-->
<mvc:mapping path="/settings/**"/>
<mvc:mapping path="/workbench/**"/>
<!--配置排除拦截的请求(优先级高)-->
<mvc:exclude-mapping path="/settings/qx/user/toLogin.do"/>
<mvc:exclude-mapping path="/settings/qx/user/login.do"/>
<!--拦截器类-->
<bean class="com.example.crm.settings.web.interceptor.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
可以看出上面的地址(http://localhost:8080/crm/settings/qx/user/toLogin.do
)要排除拦截,
由此可以看出这个地址也要配置排除拦截(/settings/qx/user/login.do
)
登录成功进去后显示地址(http://localhost:8080/crm/workbench/index.do
)
所以这个要配置拦截,必须要先验证登录才能进去,否则用户在地址栏直接输入地址不用经过登录就可以直接访问
当我在地址栏直接输入地址,未经登录验证就可以访问(存在安全性问题,这就是为什么要配置拦截器的原因)
由于我改了目录包的位置,所以这里目录包的位置也要改一下
好,进行测试
配置了拦截器,如果未经登陆验证直接输入地址(http://localhost:8080/crm/workbench/index.do
)访问访问会一直留在登录页面
<bean class="com.example.crm.settings.web.interceptor.LoginInterceptor"/>
注意这上面段代码不能写错,否则配置不起作用。(当然以上代码不需要背,了解它的作用以及含义就可以了,直接复制该路径就可以了)
创建MainController类
修改这段代码
当前index.html
修改为index.jsp
(强调一下,jsp本质一个servlet,jsp实质上会被翻译成servlet,前端代码都在out.write("")
里面)
%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
String basePath=request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+request.getContextPath()+"/";
%>
<html>
<head>
<base href="<%=basePath%>">
创建市场活动需求分析
先做创建(有数据了,才能进行其它操作)
// 查询所有的用户
List<User> selectAllUsers();
<select id="selectAllUsers" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"></include>
from tbl_user where lock_state='1'
</select>
创建ActivityController
@Controller
public class ActivityController {
@Autowired
private UserService userService;
@RequestMapping("/workbench/activity/index.do")
public String index(HttpServletRequest request) {
List<User> userList = userService.queryAllUsers();
// 把数据保存到request中
request.setAttribute("userList", userList);
// 请求转发到市场活动的主页面
return "/workbench/activity/index";
}
}
老规矩index.html
改成index.jsp
这里代码写死了
<select class="form-control" id="edit-marketActivityOwner">
<c:forEach items="${userList}" var="u">
<option value="${u.id}">${u.name}</option>
</c:forEach>
<%--<option>zhangsan</option>
<option>lisi</option>
<option>wangwu</option>--%>
</select>
测试一下
发现有部分图片没正常显示,发现路径忘了改(不得不说jsp是真麻烦)
重新运行,正常显示
<table tableName="tbl_activity" domainObjectName="Activity"
enableCountByExample="false" enableUpdateByExample="false"
enableDeleteByExample="false" enableSelectByExample="false"
selectByExampleQueryId="false">
</table>
SQL建表语句:
drop table if exists tbl_activity;
drop table if exists tbl_activity_remark;
/*==============================================================*/
/* Table: tbl_activity */
/*==============================================================*/
create table tbl_activity
(
id char(32) not null,
owner char(32),
name varchar(255),
start_date char(10),
end_date char(10),
cost varchar(255),
description varchar(255),
create_time char(19),
create_by varchar(255),
edit_time char(19),
edit_by varchar(255),
primary key (id)
);
/*==============================================================*/
/* Table: tbl_activity_remark */
/*==============================================================*/
create table tbl_activity_remark
(
id char(32) not null,
note_content varchar(255),
create_time char(19),
create_by varchar(255),
edit_time char(19),
edit_by varchar(255),
edit_flag char(1) comment '0表示未修改,1表示已修改',
activity_id char(32),
primary key (id)
);
使用代码生成器生成代码
int insertActivity(Activity activity);
这个删掉或者直接移动改一下
改一下
创建接口ActivityService
添加接口方法
@Service
public class ActivityServiceImpl implements ActivityService {
@Autowired
private ActivityMapper activityMapper;
@Override
public int saveCreateActivity(Activity activity) {
return activityMapper.insertActivity(activity);
}
}
在applicationContext.xml假如扫描包的路径(<context:component-scan base-package="com.example.crm.workbench.service" />
)
把edit_time, edit_by
两个字段删除掉,这两个用不到。
修改后代码:
<insert id="insertActivity" parameterType="com.example.crm.workbench.domain.Activity">
<!--
WARNING - @mbggenerated
This element is automatically generated by MyBatis Generator, do not modify.
This element was generated on Fri Mar 11 22:15:54 CST 2022.
-->
insert into tbl_activity (id, owner, name, start_date,
end_date, cost, description,
create_time, create_by)
values (#{id,jdbcType=CHAR}, #{owner,jdbcType=CHAR}, #{name,jdbcType=VARCHAR}, #{startDate,jdbcType=CHAR},
#{endDate,jdbcType=CHAR}, #{cost,jdbcType=VARCHAR}, #{description,jdbcType=VARCHAR},
#{createTime,jdbcType=CHAR}, #{createBy,jdbcType=VARCHAR})
</insert>
如何生成随机字符串:
、public class UUIDTest {
public static void main(String[] args) {
String uuid = UUID.randomUUID().toString();
System.out.println(uuid);
}
}
测试得到的是36位,如何得到32位呢?(使用String
类下的replaceAll
方法)
在utils
包下创建一个工具类UUIDUtils
工具类类
public class UUIDUtils {
public static String getUUID() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
}
然后回到ActivityController
引用这个工具类
@RequestMapping("/workbench/activity/saveCreateActivity.do")
public @ResponseBody
Object saveCreateActivity(Activity activity, HttpSession session) {
User user = (User) session.getAttribute(Constants.SESSION_USER);
activity.setId(UUIDUtils.getUUID());
activity.setCreateTime(DateUtils.formatDate(new Date()));
// 谁点击了创建就设置那个用户id
activity.setCreateBy(user.getId());
ReturnObject returnObject = new ReturnObject();
try {
int result = activityService.saveCreateActivity(activity);
if (result > 0) {
returnObject.setCode(Constants.RETURN_OBJECT_CODE_SUCCESS);
} else {
returnObject.setCode(Constants.RETURN_OBJECT_CODE_FAIL);
returnObject.setMessage("系统繁忙,请稍后重试");
}
} catch (Exception e) {
e.printStackTrace();
}
return returnObject;
}
回到activity
包下的index.jsp
,进行修改
$("#createActivityBtn").click(function () {
// 弹出创建市场活动的模态窗口
$("#createActivityModal").modal("show")
})
成功弹出创建市场活动的窗口
给保存按钮绑定一个事件监听事件
改一下开始日期和结束日期的id以及描述
$("#saveCreateActivityBtn").click(function() {
//收集参数
var owner=$("#create-marketActivityOwner").val();
var name=$.trim($("#create-marketActivityName").val());
var startDate=$("#create-startDate").val();
var endDate=$("#create-endDate").val();
var cost=$.trim($("#create-cost").val());
var description=$.trim($("#create-description").val());
//表单验证
if(owner==""){
alert("所有者不能为空");
return;
}
if(name==""){
alert("名称不能为空");
return;
}
if(startDate!=""&&endDate!=""){
//使用字符串的大小代替日期的大小
if(endDate<startDate){
alert("结束日期不能比开始日期小");
return;
}
}
/*
正则表达式:
1,语言,语法:定义字符串的匹配模式,可以用来判断指定的具体字符串是否符合匹配模式。
2,语法通则:
1)//:在js中定义一个正则表达式. var regExp=/...../;
2)^:匹配字符串的开头位置
$: 匹配字符串的结尾
3)[]:匹配指定字符集中的一位字符。 var regExp=/^[abc]$/;
var regExp=/^[a-z0-9]$/;
4){}:匹配次数.var regExp=/^[abc]{5}$/;
{m}:匹配m此
{m,n}:匹配m次到n次
{m,}:匹配m次或者更多次
5)特殊符号:
\d:匹配一位数字,相当于[0-9]
\D:匹配一位非数字
\w:匹配所有字符,包括字母、数字、下划线。
\W:匹配非字符,除了字母、数字、下划线之外的字符。
*:匹配0次或者多次,相当于{0,}
+:匹配1次或者多次,相当于{1,}
?:匹配0次或者1次,相当于{0,1}
*/
var regExp=/^(([1-9]\d*)|0)$/;
if(!regExp.test(cost)){
alert("成本只能为非负整数");
return;
}
//发送请求
$.ajax({
url:'workbench/activity/saveCreateActivity.do',
data:{
owner:owner,
name:name,
startDate:startDate,
endDate:endDate,
cost:cost,
description:description
},
type:'post',
dataType:'json',
success:function (data) {
if(data.code=="1"){
//关闭模态窗口
$("#createActivityModal").modal("hide");
//刷新市场活动列,显示第一页数据,保持每页显示条数不变(保留)
}else{
//提示信息
alert(data.message);
//模态窗口不关闭
$("#createActivityModal").modal("show");//可以不写。
}
}
});
})
测试一下(开始日期不能大于结束日期,成本只能为非负整数)
这里发现了一个问题,填完信息之后点击保存之后,再点击创建之后发现数据还在(这应该是编辑才有的功能,保存之后再点击创建数据是想显示重置的)
因此我们在点击创建按钮的同时应该重置表单创建
$("#createActivityForm").get(0).reset()
测试一下表单成功
由于所有者这段代码已经写死了,所有重置之后数据仍然会显示
1,js日历:
一类问题:
1)实现起来比较复杂。
2)跟业务无关。
日历插件:bootstrap-datetimepicker
前端插件使用步骤:
1)引入开发包:.js,.css
下载开发包,拷贝到项目webapp目录下
把开发包引入到jsp文件中:
演示日期插件功能
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
String basePath=request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+request.getContextPath()+"/";
%>
<html>
<head>
<base href="<%=basePath%>">
<!--JQUERY-->
<script type="text/javascript" src="jquery/jquery-1.11.1-min.js"></script>
<!--BOOTSTRAP框架-->
<link rel="stylesheet" type="text/css" href="jquery/bootstrap_3.3.0/css/bootstrap.min.css">
<script type="text/javascript" src="jquery/bootstrap_3.3.0/js/bootstrap.min.js"></script>
<!--BOOTSTRAP_DATETIMEPICKER插件-->
<link rel="stylesheet" type="text/css" href="jquery/bootstrap-datetimepicker-master/css/bootstrap-datetimepicker.min.css">
<script type="text/javascript" src="jquery/bootstrap-datetimepicker-master/js/bootstrap-datetimepicker.min.js"></script>
<script type="text/javascript" src="jquery/bootstrap-datetimepicker-master/locale/bootstrap-datetimepicker.zh-CN.js"></script>
<title>演示bs_datatimepicker插件</title>
<script type="text/javascript">
$(function () {
//当容器加载完成,对容器调用工具函数
$("#myDate").datetimepicker({
language:'zh-CN', //语言
format:'yyyy-mm-dd',//日期的格式
minView:'month', //可以选择的最小视图
initialDate:new Date(),//初始化显示的日期
autoclose:true//设置选择完日期或者时间之后,日否自动关闭日历
});
});
</script>
</head>
<body>
<input type="text" id="myDate">
</body>
</html>
启动Tomcat小猫咪
这是日期插件的数据显示
这里把clear
改为中文"清空
“”
改一下这个路径
todayBtn:true,//设置是否显示"今天"按钮,默认是false
clearBtn:true //设置是否显示"清空"按钮,默认是false
给创建市场活动添加日期功能
首先在index.jsp修改日期jar包路径
<script type="text/javascript" src="jquery/bootstrap-datetimepicker-master/js/bootstrap-datetimepicker.js"></script>
<script type="text/javascript" src="jquery/bootstrap-datetimepicker-master/locale/bootstrap-datetimepicker.zh-CN.js"></script>
这里为了减少代码冗余使用class选择器,而不是id选择器,id选择器不能重复,而class选择器可以重复多个。
$(".myDate").datetimepicker({
language:'zh-CN', //语言
format:'yyyy-mm-dd',//日期的格式
minView:'month', //可以选择的最小视图
initialDate:new Date(),//初始化显示的日期
autoclose:true,//设置选择完日期或者时间之后,日否自动关闭日历
todayBtn:true,//设置是否显示"今天"按钮,默认是false
clearBtn:true //设置是否显示"清空"按钮,默认是false
});
测试一下,可以显示
十一.分页查询市场活动
1,前端插件的使用步骤:
1)引入开发包:.css,.js
下载开发包,
引入jsp页面,先引入被依赖技术的开发包。
2)创建容器:
3)当容器加载完成之后,对容器调用工具函数:
2,在指定的标签中显示jsp页面片段:
选择器.html(jsp页面片段的字符串);//覆盖显示
选择器.append(jsp页面片段的字符串);//追加显示
选择器.after(jsp页面片段的字符串);
选择器.before(jsp页面片段的字符串);
选择器.text(jsp页面片段的字符串);
在ActivityMapper.xml
添加接口方法
List<Activity> selectActivityByConditionForPage();
<select id="selectCountOfActivityByCondition" parameterType="map" resultType="int">
select count(*)
from tbl_activity a
join tbl_user u1 on a.owner=u1.id
join tbl_user u2 on a.create_by=u2.id
left join tbl_user u3 on a.edit_by=u3.id
<where>
<if test="name!=null and name!=''">
and a.name like '%' #{name} '%'
</if>
<if test="owner!=null and owner!=''">
and u1.name like '%' #{owner} '%'
</if>
<if test="startDate!=null and startDate!=''">
and a.start_date>=#{startDate}
</if>
<if test="endDate!=null and endDate!=''">
and a.end_date<=#{endDate}
</if>
</where>
</select>
一条selec语句执行的顺序:
分页查询市场活动实现查询市场活动列表Service层
List<Activity> queryActivityByConditionForPage(Map<String, Object> map);
@Override
public List<Activity> queryActivityByConditionForPage(Map<String, Object> map) {
return activityMapper.selectActivityByConditionForPage(map);
}
ActivityMapper类中加入方法
List<Activity> queryActivityByConditionForPage(Map<String, Object> map);
ActivityService加入方法
int queryCountOfActivityByCondition(Map<String,Object> map);
实现类ActivityServiceImpl
@Override
public int queryCountOfActivityByCondition(Map<String, Object> map) {
return activityMapper.selectCountOfActivityByCondition(map);
}
然后回到ActivityController
类
@RequestMapping("/workbench/activity/queryActivityByConditionForPage.do")
public @ResponseBody Object queryActivityByConditionForPage(String name,String owner,
String startDate,String endDate,
int pageNo,int pageSize) {
//封装参数
Map<String,Object> map=new HashMap<>();
map.put("name",name);
map.put("owner",owner);
map.put("startDate",startDate);
map.put("endDate",endDate);
map.put("beginNo",(pageNo-1)*pageSize);
map.put("pageSize",pageSize);
//调用service层方法,查询数据
List<Activity> activityList=activityService.queryActivityByConditionForPage(map);
int totalRows=activityService.queryCountOfActivityByCondition(map);
//根据查询结果结果,生成响应信息
Map<String,Object> retMap=new HashMap<>();
retMap.put("activityList",activityList);
retMap.put("totalRows",totalRows);
return retMap;
}
设置id
发齐ajax请求实现分页
function queryActivityByConditionForPage() {
//收集参数
var name=$("#query-name").val();
var owner=$("#query-owner").val();
var startDate=$("#query-startDate").val();
var endDate=$("#query-endDate").val();
var pageNo=1;
var pageSize=10;
//发送请求
$.ajax({
url:'workbench/activity/queryActivityByConditionForPage.do',
data:{
name:name,
owner:owner,
startDate:startDate,
endDate:endDate,
pageNo:pageNo,
pageSize:pageSize
},
type:'post',
dataType:'json',
success:function (data) {
//显示总条数
$("#totalRowsB").text(data.totalRows);
//显示市场活动的列表
//遍历activityList,拼接所有行数据
var htmlStr="";
$.each(data.activityList,function (index,obj) {
htmlStr+="<tr class=\"active\">";
htmlStr+="<td><input type=\"checkbox\" value=\""+obj.id+"\"/></td>";
htmlStr+="<td><a style=\"text-decoration: none; cursor: pointer;\" οnclick=\"window.location.href='detail.html';\">"+obj.name+"</a></td>";
htmlStr+="<td>"+obj.owner+"</td>";
htmlStr+="<td>"+obj.startDate+"</td>";
htmlStr+="<td>"+obj.endDate+"</td>";
htmlStr+="</tr>";
});
$("#tBody").html(htmlStr);
}
});
添加id
注释掉这段代码
修改以下js
//当市场活动主页面加载完成,查询所有数据的第一页以及所有数据的总条数,默认每页显示10条
queryActivityByConditionForPage();
//给"查询"按钮添加单击事件
$("#queryActivityBtn").click(function () {
//查询所有符合条件数据的第一页以及所有符合条件数据的总条数;
queryActivityByConditionForPage();
});
给查询按钮绑定id
测试一下:
发现数据不存在了。