本次demo采用SSM整体架构,数据库使用的mysql,安全权限控制框架使用的是shiro(前后端),目标是电商的后管系统。包含,权限管理、用户管理、产品分类、商品管理、订单管理,五大模块。从今天开始开始复习本demo。从权限管理开始。
如何从servlet项目到SSM的整体思维转移
其实本质底层还是基于HTTP携带数据,进行相关的转发或者重定向,亦或是session或者cookie的处理。servlet本质是ssm的基础,代码更接近底层。
如何对应呢?
1.首先知道什么是SSM-Spring、SpringMVC、Mybaties三大框架。
2.Spring
在上一个servlet的版本中我们每层的实例化对象都是我们new出来的,所以耦合度很大。现在我们交给spring来管理。无论是bean、还是控制层、服务层、持久层我们都通过spring来获取(这里说下杨老师的指导,servlet和spring对于对象管理这块,比较像我们骑自行车和开汽车的区别:骑自行车的时候,其实还是我们在给自行车动力方向,我们实际上还是带着自行车在向前走,但是汽车就不一样,汽车属于带着我们向前走)当然还有一种常见的理解,菜篮子理论,以前你是主动去菜市场买菜,现在就把篮子放在门口,等着社区工作人员给你送上门来就行。暂时我理解的也没多少自己的独特点。spring就说到这,记住就是管理一切以前需要new的就行(其实不仅仅这些,暂时先说这些)。
3.SpringMVC
我们以前的控制层代码思路是通过获取httprequest中的url来分辨,数据流的方向。现在我们通过注解直接加到,可以实现对数据流的控制,包括ajax的相应、转发重定向。至于返回页面这块,我们以前的代码大多数多于两行,现在我们通过前端解析器的配合,使得转发或者更加简单。
4.Mybaties
以前的dao层我们是自己书写sql语句,不仅仅十分繁重的任务其实冗余也很大,并且没有基于实体类这种sql语句(当时基于业务逻辑,其实粒度很细,但是语句之间有很多可以抽离出来共性的东西。)这次采用mybaties是因为他是一个半自动化的dao层执行软件,半自动化意味着我们可以根据实际需求相应做一些语句的增加和改变。
本身后管系统是基于用户、角色、权限这一模型而建立的。他们三者,存在从左到右的一对多或者多对多关系。具体实现的效果是,不同用户拥有不同角色、不同角色又拥有不同权限。我们这里说下权限,在本demo里面的定义。权限分为两种一种是菜单,一种是权限(操作)。实现效果如图所示。
左侧菜单栏代表上述所说菜单,不同的角色菜单不同,然后再细化,比如说权限管理下面的编辑删除这样的操作,不同的角色也都不一样,有些基础橘色压根就没有这两个按钮,当然有些角色左侧菜单栏有的也没有。
设计流程,自底向上
要实现上述的话首先最基本的三张表,用户表,角色表,权限表。另外我们在这个项目中并没有使用外键,所以我们还需要两张关联表。用户—角色表、角色权限表。
再说dao层,我们这里涉及到第一次使用mybaties,有一个词你要知道,逆向工程。*
何为逆向工程*–通过我们设计好的数据库,我们从反向生成三样东西。
bean实例化对象(你比如说上面的user role permission这三个实体类,当然还有对应的example对象,以后再说),dao层接口(像usermapper这样的接口),以及对应的各个类的xml文件(像usermapper.xml这样的xml文件)。在我们生成以后我们将bean对象、以及dao层接口都交给spring来管理。方便以后我们在control层以及service层的使用。@注解加上(具体细节,自行查阅官方文档)
到此,我们的MVC模式就差control层和service层了。
control层仍然负责接受前端的请求,调用service层相应函数,service层调用dao层。只是上文提起过在ssm里面做了代码简化。
大概看下代码。
控制层
@Controller
public class Logincontroller {
@Autowired
UserService service;
@Autowired
RoleService roleService;
@Autowired
permissionService permissionService;
@RequestMapping("/user/login")
@ResponseBody
public Msg login(User user) throws Exception{
service层
@Service
public class permissionService {
@Autowired
PermissionMapper permissionMapper;
@Autowired
RoleMapper roleMapper;
@Autowired
RpMapper rpMapper;
@Autowired
RpExample rpExample;
// fc1
// 登录以后页面菜单的渲染
public List<Permission> getPermissionsByUserId(Integer getuId) {
// 查出这个用户 什么角色 这个角色有什么菜单
List<Permission> menus=permissionMapper.getPermissionsByUserId(getuId);
return menus;
}
dao层
@Component
public interface AddressMapper {
int countByExample(AddressExample example);
int deleteByExample(AddressExample example);
00.安全的前端页面位置,中央的跳转控制器。
这块的话,我们把所有前端页面都放在web-inf文件下,我们知道从服务器外直接上url是不可行的,同时我们上服务器的话又有下面说的shiro等模块。(暂时没加验证码、MD5(直接明文存在数据库里面的))。。还是比较安全的。
1.
ajax后端认证成功后,ajax重定向(此时系统里面已经有对应的session生成,所以如果不是外面直接伪造session还是比较安全的)
location.href="${APP_PATH}/page_index";//跳转首页
跳转控制器
public class indexcontrol {
// 只是登录使用--这个请求直接带到web-inf下面所有的jsp页面
@RequestMapping("/page_{na}")
public String index1(@PathVariable("na")String na){
return na;
}
0.shiro安全框架的引用,因为我们是从登录以后进入这个权限管理模块,每个人都有不同角色,后期我们要通过shiro的前后端角色验证实现一些功能,所以第一个要介绍的是shiro框架。直接说在这个项目里面的使用吧,具体细节大家去网上找资料。myRealm模块(登录模块、授权模块)权限配置、 主体比较重要。
自定义realm模块主要是认证和授权两部分:认证就是在登录成功以后的一个认证(其实也不是我们只是没有用,shiro可以直接与你数据库用户表相关联,他能帮你完成登录认证,我们是登陆以后生成token。。)。。。授权比较重要我们在这个模块加入角色的相关权限。
授权关联着权限配置-----权限配置.xml
大概看下最关键的地方–这个地方有几点要说的
第一,登录页面不要拦截,同时登录的请求动作也不要拦截,不然你会遇见服务器死循环的(前提是你的拒绝页面就是登录页面。)
第二,静态资源,比如说前端的js这些css都放走。
第三,比较重要理解这个格式。
/permission/list= perms[permission:list]
你要访问这个xx list这个请求,你首先得有permission:list这样的权限在数据库中,也就是有这样的字符串记录
/login.jsp=anon
/user/login=anon
/static/**=anon
/permission/list= perms[permission:list]
/permission/edi=perms[permission:edi]
/permission/del=perms[permission:del]
1.不同角色页面的渲染-shiro的前端控制
我们通过登录以后加入session中的菜单信息,再次从前端取出渲染到页面上,数据库有相应的url我们就生成相应的菜单项。
另外说下shiro的前端认证,他会看你数据库的权限配置
数据库中:
权限编辑:permission:edi
前端验证
<shiro:hasPermission name="permission:edi">
<td>编辑操作</td>
</shiro:hasPermission>
(其实也可以写成前端取判断字符串,只是这个框架比较简洁了)
逆向bug
1.逆向工程的时候一定要注意了,如果是mac系统请关注斜杠方向,估计linux也是。这块出现过一直不出逆向文件的bug
<javaModelGenerator targetPackage="com.bean"
targetProject="./src/main/java">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
javaModelGenerator>
2.常常遇见bug,说重复读bug
Result Maps collection already contains value for com.dao.PermissionMapper.BaseResultMap
2021.1.16
多次遇到的mybaties重复读问题
第一种解决方案
在mybaties配置文件中 加入这个suppress
<context id="DB2Tables" targetRuntime="MyBatis3">
<commentGenerator>
<property name="suppressAllComments" value="true" />
commentGenerator>
第二种解决方案
报错信息 直接拉到最后 他说哪个文件 你就真的进去找
像我这次 就是一个resultmap 准备要改扔到那里 没改
所以跟原来的重复率 就直接报错。
第二种就是人为大意,这块的bug你要拉到最后前面说的都是影响,而且最后他也不一定给你说人话。还是有点靠经验的。
还是dao层这点事
一对多的冲突问题,最早bug出现以后,我们定位于page版本问题,确实这个问题以前困扰过我。page版本的话你要看数据库,如果数据库是8点几,对应的page依赖版本要到5.1.4以上。
后来版本解决问题了,但是还是有bug:具体表现为查询所有角色对应的权限只能查到第一条。与下文,博主情况十分相似。
// 用于展示 权限分页信息
@RequestMapping("/Userpermission/list/{num}")
@ResponseBody
public Msg getAllRoleWithPermisssion(@PathVariable("num") Integer num){
// 一旦启用这个分页 service层原来查出来的list集合也会做改变
// 第一次做测试的时候 先把分页注释掉
PageHelper.startPage(num,3);
System.out.println(num);
List<Role> roles=rservice.getAllRoleWithPermission(null);
// xml 依赖 1.6 bug 后端list不输出 版本问题 pagehelper
PageInfo pageinfo=new PageInfo<>(roles,5);
最后我们定位在 修改xml中。一对多冲突问题,还与mybaties本身机制有关系,暂时讲解决方案,底下这篇文章有些帮助。
转载
https://www.cnblogs.com/zemliu/p/3263053.html
改造xml
主查询
查询开始 注意返回的结果集 以及select后面简单的什么都没有 去重也不要有!!!
BaseResultMapWithPermissionWithROleId
<select id="selectByExampleWithPermission" parameterType="com.bean.RoleExample" resultMap="BaseResultMapWithPermissionWithROleId">
select
<include refid="Base_Column_List" />
from role
select>
结果集展示
<resultMap id="BaseResultMapWithPermissionWithROleId" type="com.bean.Role">
<id column="r_id" jdbcType="INTEGER" property="rId" />
<result column="r_name" jdbcType="VARCHAR" property="rName"/>
<collection property="permissions" javaType="list" ofType="com.bean.Permission" column="r_id" select="selectPermissionByRoleId">
collection>
resultMap>
子查询语句
<select id="selectPermissionByRoleId" parameterType="java.lang.Integer" resultMap="BaseResultMapinRoleMapper">
select
<include refid="Base_Column_List_With_PermissionWith_roleId" />
from role r inner join r_p rp on r.r_id=rp.rp_rid inner join permission p on rp.rp_pid=p.p_id
where r.r_id = #{rId}
select>
其实这个问题,我们在servlet版本中,简单处理过,当时写了一个判断算法去判断一对多关系的结束与开始,其实后面考虑直接使用hashmap解决问题就可以了。
当然上面的修改查询语句还是有点复杂(仁者见仁)我本人还是推荐在service层用各种方法改造加到一的里面,将多的集合加到一的里面。也就是说可以多利用条件查询等方法在service层构造联合查询。
删除一对多的情况里 没有改xml
// fc5
// 删除当前角色拥有的权限 以及当前角色信息
public boolean permissiondeleteOne(int rid){
// 第一要删除这个角色
int flag1=roleMapper.deleteByPrimaryKey(rid);
// 第二删除的是 这个角色与权限表关联
//我们就不改mapper了 靠角色id 删除
RpExample.Criteria c=rpExample.createCriteria();
c.andRpRidEqualTo(rid);
int flag2=rpMapper.deleteByExample(rpExample);
if (flag1!=0&flag2!=0){
return true;
}
else {
return false;
}
}
另外一种不算一对多的一对多,,,包含多个信息吧,这时候我觉得改xml还是容易出错的。
展示一种情况的分步查询–后面的订单模块的订单
简单说下,实体类
indent 订单类
Buyuser 买家类
Receiving 物流信息( 收货信息)
address 收货地址
public List<Indent> getall(){
List<Indent> indents=indentMapper.selectByExample(null);
// 再通过 返回的订单信息查找对应的买家 以及收货人信息 做遍历
for (Indent indent:indents){
// 获取这个买家的信息
Buyuser buyuser=buyuserMapper.selectByPrimaryKey(indent.getIbuyId());
// 获取收货人信息
Receiving receiving=receivingMapper.selectByPrimaryKey(indent.getReceivingId());
// 收货地址根据收货人信息 查出来以后先加到收货信息里面
Address address=addressMapper.selectByPrimaryKey(Integer.parseInt(receiving.getReceivingAddress()));
receiving.setAddress(address);
// 将这两个属性加到这一条订单信息中
indent.setBuyuser(buyuser);
indent.setReceiving(receiving);
}
return indents;
}
// A code block
sout(“Hello world!”)';
待总结的点 说说spring springmvc拦截器 各种各样的注释吧