1一、基于Springboot+MybatisPlus+Echarts+Mysql实现各种疫情地图及管理系统
该项目基于springboot框架实现了疫情背景下的校园管理,主要涵盖了中国疫情确诊分布地图(对接腾讯API接口)、中国实时疫情新闻播报、以及对疫情数据的饼图、折线图和柱状图展示。系统角色可以进行增删改查,为角色分配菜单权限,大致分为学生、教师、院系级系统管理员,集成了shiro框架实现了不同的角色可以赋予不同的菜单栏权限,可以完成菜单栏的动态增删改查,实现了动态的权限设置。
在校园疫情数据管理中,基于mybatiplus框架实现了疫情数据的带有条件查询及分页的增删改查,并实现了拖拽式上传excel数据和导出疫情数据。其中相关功能主要包含疫情数据管理,疫情新闻管理,疫情风险地区查询,近30日疫情新增趋势等等,疫情图表展示管理,学生健康打卡管理,院系管理、班级管理、核酸检测管理,疫苗接种管理,学生请假管理等等。系统管理中主要包含了用户管理、角色管理和菜单管理。
在数据库设计中,主要设计了用户与角色之间的多对多实体关系,角色与菜单之间的多对多关系设计。其中,学生请假功能设计了详细的审批流,学生提交审批流后,教师审批后方为院系审批,为串行审批流程,设计了审批节点的状态与审批流的审批逻辑。
在缓存设计中,主要将访问量较高的中国疫情地图和新闻播报首页进行了redis数据缓存,为了保证redis缓存与mysql数据的一致性,每当定时任务触发去解析腾讯API接口数据时候,每更新一次数据库数据,就要删除一次redis缓存,这样就可以保证客户端每次查询数据查不到就可以进行访问数据库更新最新的缓存数据。
在接口设计中,采用了RestFul风格的架构,使请求路径变得更加简洁,传递、获取参数值更加方便,通过请求路径中直接传递参数值,不会暴露传递给方法的参数变量名,接口也变得更加安全。
功能继续更新中【BILIBILI…】
【Coding路人王:从0到1】
上课地址:https://www.bilibili.com/video/BV1aY411c7d1?share_source=copy_web
源码下载:源码下载:https://download.csdn.net/download/wyn_365/87097570
已完成展示
功能继续更新中【BILIBILI…】
1.1 构建springboot项目
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/nocv?serverTimezone=UTC&useSSL=false&characterEncoding=utf-8
driver-class-name: com.mysql.jdbc.Driver
1.2 引入Echarts地图
1.官网:https://echarts.apache.org/zh/ 下载JS文件引入项目
2.查看图例
3.快速使用
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- 引入刚刚下载的 ECharts 文件 -->
<script src="echarts.js"></script>
</head>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>ECharts</title>
<!-- 引入刚刚下载的 ECharts 文件 -->
<script src="echarts.js"></script>
</head>
<body>
<!-- 为 ECharts 准备一个定义了宽高的 DOM -->
<div id="main" style="width: 600px;height:400px;"></div>
<script type="text/javascript">
// 基于准备好的dom,初始化echarts实例
var myChart = echarts.init(document.getElementById('main'));
// 指定图表的配置项和数据
var option = {
title: {
text: 'ECharts 入门示例'
},
tooltip: {},
legend: {
data: ['销量']
},
xAxis: {
data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
},
yAxis: {},
series: [
{
name: '销量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20]
}
]
};
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
</script>
</body>
</html>
地图社区图例:http://www.isqqw.com/
1.3 创建数据库
DROP TABLE IF EXISTS `nocv_data`;
CREATE TABLE `nocv_data` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`value` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=35 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of nocv_data
-- ----------------------------
INSERT INTO `nocv_data` VALUES ('1', '澳门', '95');
INSERT INTO `nocv_data` VALUES ('2', '香港', '35');
INSERT INTO `nocv_data` VALUES ('3', '台湾', '153');
INSERT INTO `nocv_data` VALUES ('4', '新疆', '56');
INSERT INTO `nocv_data` VALUES ('5', '宁夏', '26');
INSERT INTO `nocv_data` VALUES ('6', '青海', '26');
1.4 编写代码
springboot
contRoller: /query
service:
dao:
entity:
1.5 展示数据
$.ajax({
url: "/query",
dataType: "json",
success: function (data) {
// 某种意义上来说,数组也是object
for (let i in data) {
dataList[i] = data[i];
}
myChart.setOption({
series: [
{
name: "确诊病例",
type: "map",
geoIndex: 0,
data: dataList
}
]
});
}
});
七、中国疫情地图增删改查
7.1 分页配置MybatisPlusConfig
@Configuration
@ConditionalOnClass(value = {PaginationInterceptor.class})
public class MybatisPlusConfig {
@Bean
public PaginationInterceptor paginationInterceptor(){
return new PaginationInterceptor();
}
}
7.2 layui返回的数据格式 DataView
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DataView {
private Integer code = 0;
private String msg = "";
private Long count = 0L;
private Object data;
public DataView(Long count,Object data){
this.count = count;
this.data = data;
}
public DataView(Object data){
this.data = data;
}
}
八、疫情打卡上报管理
8.1 引入静态打卡HTML页面
九、实现拖拽Excel导入疫情数据功能
9.1 引入pom依赖
<!--引入poi-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.0.0</version>
</dependency>
9.2 引入layui 的拖拽上传Excel组件
* Excel的拖拽或者点击上传
* 1.前台页面发送一个请求,上传文件MutilpartFile HTTP
* 2.Controller,上传文件MutilpartFile 参数
* 3.POI 解析文件,里面的数据一行一行全部解析出来
* 4.每一条数据插入数据库
* 5.mybatiplus 批量saveBatch(list)
<div class="layui-upload-drag" id="test10">
<i class="layui-icon"></i>
<p>点击上传,或将文件拖拽到此处</p>
<div class="layui-hide" id="uploadDemoView">
<hr>
<img src="" alt="上传成功后渲染" style="max-width: 196px">
</div>
</div>
layui.use(['upload','jquery'],function(){
var layer = layui.layer //弹层
,$ = layui.jquery
,upload = layui.upload
//拖拽上传
upload.render({
elem: '#test10'
,url: '/excelImport'
,accept: 'file' //普通文件
,done: function(res){
layer.msg('上传成功');
console.log(res);
}
});
9.2 编写Controller
// Excel数据导入
@RequestMapping(value = "/excelImport", method = RequestMethod.POST)
@ResponseBody
public DataView uploadExcel(@RequestParam("file") MultipartFile file) {
DataView dataView = new DataView();
if (file.isEmpty()) {
dataView.setMsg("文件为空");
return dataView;
}
try {
//根据路径获取这个操作excel的实例
HSSFWorkbook wb = new HSSFWorkbook(file.getInputStream());
HSSFSheet sheet = wb.getSheetAt(0);
//实体类集合
List<NocvData> listData = new ArrayList<>();
HSSFRow row = null;
//循环sesheet页中数据从第二行开始,第一行是标题
for (int i = 0; i < sheet.getPhysicalNumberOfRows(); i++) {
//获取每一行数据
row = sheet.getRow(i);
NocvData data = new NocvData();
data.setName(row.getCell(0).getStringCellValue());
data.setValue(Integer.valueOf((int) row.getCell(1).getNumericCellValue()));
listData.add(data);
}
//循环展示导入的数据,实际应用中应该校验并存入数据库
indexService.saveBatch(listData);
dataView.setCode(200);
dataView.setMsg("导入成功");
return dataView;
} catch (Exception e) {
e.printStackTrace();
}
dataView.setCode(100);
dataView.setMsg("导入失败");
return dataView;
}
9.4 数据导出Excel功能【中国疫情数据】
1.前端发送请求 /
2.后端查询数据库,封装数据Excel实体
3.返回数据建立输出,写出浏览器文件
// 导出疫情数据
form.on("submit(doExport)",function () {
window.location.href="/excelOutportChina";//这里是接口的地址
})
<button type="button" class="layui-btn layui-btn-sm layui-btn-radius" lay-submit="" lay-filter="doExport"><i class="layui-icon layui-icon-search layui-icon-normal"></i>导出中国疫情数据Excel
</button>
@RequestMapping("/excelOutportChina")
@ResponseBody
public void excelOutportChina(HttpServletResponse response){
response.setCharacterEncoding("UTF-8");
List<NocvData> list = indexService.list();
HSSFWorkbook wb = new HSSFWorkbook();
//2-创建sheet页,设置sheet页的名字
HSSFSheet sheet = wb.createSheet("中国数据表");
//3-创建标题行
HSSFRow titleRow = sheet.createRow(0);
titleRow.createCell(0).setCellValue("城市名称");
titleRow.createCell(1).setCellValue("确诊数量");
//4-遍历将数据集合将数据放到对应的列中
for (NocvData data : list){
HSSFRow dataRow = sheet.createRow(sheet.getLastRowNum()+1);
dataRow.createCell(0).setCellValue(data.getName());
dataRow.createCell(1).setCellValue(data.getValue());
}
// 5.建立输出
OutputStream os = null;
try{
//6-设置Excel的名称
response.setContentType("application/octet-stream;charset=utf-8");
response.setHeader("Content-Disposition", "attachment;filename="
+ new String("中国疫情数据表".getBytes(),"iso-8859-1") + ".xls");
os = response.getOutputStream();
wb.write(os);
os.flush();
}catch(Exception e){
e.printStackTrace();
} finally {
try {
if(os != null){
os.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
十、对接腾讯API接口实现疫情数据实时更新
十、打开腾讯数据网址腾讯实时疫情
**主页网址:**https://news.qq.com/zt2020/page/feiyan.htm#/global
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6br5VFGz-1654695993012)(C:\Users\15067\AppData\Local\Temp\1654132815620.png)]
**腾讯数据接口:**https://view.inews.qq.com/g2/getOnsInfo?name=disease_h5
**网易数据接口:**https://c.m.163.com/ug/api/wuhan/app/data/list-total
10.1 网络爬虫对接 【腾讯】API接口
<!--httpClient客户端-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.56</version>
</dependency>
@Component
public class HttpUtils {
@Bean
public String getData() throws IOException {
//请求参数设置
RequestConfig requestConfig = RequestConfig.custom()
//读取目标服务器数据超时时间
.setSocketTimeout(10000)
//连接目标服务器超时时间
.setConnectTimeout(10000)
//从连接池获取连接的超时时间
.setConnectionRequestTimeout(10000)
.build();
CloseableHttpClient httpClient = null;
HttpGet request = null;
CloseableHttpResponse response = null;
try {
//创建HttpClient
httpClient = HttpClients.createDefault();
//使用url构建get请求
request = new HttpGet("https://c.m.163.com/ug/api/wuhan/app/data/list-total");
//填充请求设置
request.setConfig(requestConfig);
//发送请求,得到响应
response = httpClient.execute(request);
//获取响应状态码
int statusCode = response.getStatusLine().getStatusCode();
//状态码200 正常
if (statusCode == 200) {
//解析响应数据
HttpEntity entity = response.getEntity();
//字符串格式数据
String string = EntityUtils.toString(entity, "UTF-8");
System.out.println("字符串格式:" + string);
return string;
} else {
throw new HttpResponseException(statusCode, "响应异常");
}
} finally {
if (response != null) {
response.close();
}
if (request != null) {
request.releaseConnection();
}
if (httpClient != null) {
httpClient.close();
}
}
}
}
10.2 对接定时程序数据入库
@Controller
public class TengXunApi {
public static void main(String[] args) throws Exception {
HttpUtils httpUtils = new HttpUtils();
String string = httpUtils.getData();
System.out.println("string:"+string);
//json格式数据
JSONObject jsonObject = JSONObject.parseObject(string);
Object data = jsonObject.get("data");
System.out.println(data.toString());
System.out.println("====================================");
//=========================
JSONObject jsonObject1 = JSONObject.parseObject(data.toString());
ChinaTotal chinaTotal = (ChinaTotal) jsonObject1.get("chinaTotal");
System.out.println(chinaTotal);
}
}
10.3 对接腾讯API实现省份数据的自动刷新
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JZNONU26-1654695993014)(C:\Users\15067\AppData\Local\Temp\1654302358239.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5QhK2YJn-1654695993020)(C:\Users\15067\AppData\Local\Temp\1654302429447.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lJ3yQKhh-1654695993022)(C:\Users\15067\AppData\Local\Temp\1654303017808.png)]
// 各个省份的数据
// 3.世界各个国家及地区的所有数据
Object areaTree = jsonObjectData.get("areaTree");
System.out.println("areaTree:"+areaTree);
JSONArray areaTree1 = jsonObjectData.getJSONArray("areaTree");
Object[] objects = areaTree1.toArray();
// 所有国家的名字
for (int i = 0; i < objects.length; i++) {
JSONObject jsonObject1 = JSONObject.parseObject(objects[i].toString());
Object name = jsonObject1.get("name");
//System.out.println(name);
}
// 数组中第三个为中国省份数据
JSONObject jsonObject1 = JSONObject.parseObject(objects[2].toString());
JSONArray children1 = jsonObject1.getJSONArray("children");
Object[] objects1 = children1.toArray();
// 遍历中国地区的数据
List<NocvData> list = new ArrayList<>();
SimpleDateFormat format2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for (int i = 0; i < objects1.length; i++) {
NocvData nocvData = new NocvData();
JSONObject jsonObject2 = JSONObject.parseObject(objects1[i].toString());
Object name = jsonObject2.get("name");//省份名称
Object total2 = jsonObject2.get("total");
JSONObject totalJson = JSONObject.parseObject(total2.toString());
Object confirm2 = totalJson.get("confirm");//quzhen数量
System.out.println(name+":"+confirm2);
//获取省份更新的时间
Object extData = jsonObject2.get("extData");
JSONObject extDataJson = JSONObject.parseObject(extData.toString());
Object lastUpdateTime1 = extDataJson.get("lastUpdateTime");
//封装数据
nocvData.setName(name.toString());
nocvData.setValue(Integer.parseInt(confirm2.toString()));
String s = String.valueOf(lastUpdateTime1);
if (lastUpdateTime1 == null){
nocvData.setUpdateTime(new Date());
}else {
nocvData.setUpdateTime(format2.parse(s));
}
list.add(nocvData);
}
// 插入数据库各个省份的数据
indexService.saveBatch(list);
十一、完成系统登录和验证码
11.1 简单登录过程
前台:User: username password 【javabean】
后台:SQL:select * from user where username = ? and password = ?
USER: ID USERNAME PASSWORD IMG ROLE BANJI
session 浏览器第一次访问程序服务端,会产生一个session,会帮你生成一个唯一的ID 【缓存、数据库】
浏览器:cookies, sessionid ------- sesssion
缺点:
1.服务端压力【存储】
session保存在服务端,一个用户保存,百万级别的用户,都不保存在服务端。 session sessionid,浏览器保存id
2.局限性,浏览器禁用cookie
问题:user信息
token: 没有状态,username password 字符串
11.2 Shiro框架
登录:认证,,授权
认证:登录,学生—》学校 大门口进门
**授权:**学生=男生 【男生宿舍 男生厕所】
role【学生 老师 管理员】
学生:查看
老师:修改
管理员:删除
11.3 验证码比较难?
1.先要判断验证码对不对?
2.username password SQL
3.SHIRO 权限【角色】
吃饭时候:跑外面 砍一棵大树 一双筷子
使用。
11.2 验证码逻辑
推荐
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.6.8</version>
</dependency>
@RequestMapping("/getCode")
public void getCode(HttpServletResponse response, HttpSession session) throws IOException {
//HuTool定义图形验证码的长和宽,验证码的位数,干扰线的条数
LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(116, 36,4,10);
session.setAttribute("code",lineCaptcha.getCode());
try {
ServletOutputStream outputStream = response.getOutputStream();
lineCaptcha.write(outputStream);
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
不推荐 麻烦
@WebServlet("/checkCode")
public class CheckCodeServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
//服务器通知浏览器不要缓存
response.setHeader("pragma","no-cache");
response.setHeader("cache-control","no-cache");
response.setHeader("expires","0");
//在内存中创建一个长80,宽30的图片,默认黑色背景
//参数一:长
//参数二:宽
//参数三:颜色
int width = 80;
int height = 30;
BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
//获取画笔
Graphics g = image.getGraphics();
//设置画笔颜色为灰色
g.setColor(Color.GRAY);
//填充图片
g.fillRect(0,0, width,height);
//产生4个随机验证码,12Ey
String checkCode = getCheckCode();
//将验证码放入HttpSession中
request.getSession().setAttribute("CHECKCODE_SERVER",checkCode);
//设置画笔颜色为黄色
g.setColor(Color.YELLOW);
//设置字体的小大
g.setFont(new Font("黑体",Font.BOLD,24));
//向图片上写入验证码
g.drawString(checkCode,15,25);
//将内存中的图片输出到浏览器
//参数一:图片对象
//参数二:图片的格式,如PNG,JPG,GIF
//参数三:图片输出到哪里去
ImageIO.write(image,"PNG",response.getOutputStream());
}
/**
* 产生4位随机字符串
*/
private String getCheckCode() {
String base = "0123456789ABCDEFGabcdefg";
int size = base.length();
Random r = new Random();
StringBuffer sb = new StringBuffer();
for(int i=1;i<=4;i++){
//产生0到size-1的随机值
int index = r.nextInt(size);
//在base字符串中获取下标为index的字符
char c = base.charAt(index);
//将c放入到StringBuffer中去
sb.append(c);
}
return sb.toString();
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request,response);
}
}
十二、 集成Shiro完成登录和资源控制
12.1 简介
登陆之前:除了登录页面和静态资源之外全部拦截掉
Subject**:**主体,应用代码直接交互的对象是 Subject,也就是说 Shiro 的对外 API 核心就是 Subject , 代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;
SecurityManager**:**安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;
Realm**:**域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。
12.2 pom依赖
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.2</version>
</dependency>
<!--shiro和thymeleaf集成的扩展依赖,为了能在页面上使用xsln:shiro的标签-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
12.3 编写UserRealm
public class UserRealm extends AuthorizingRealm {
@Autowired
@Lazy
private UserService userService;
@Override
public String getName() {
return this.getClass().getSimpleName();
}
/**
* 认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//1.查询数据库
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username",token.getPrincipal().toString());
User user = userService.getOne(queryWrapper);
if (null != user){
//盐 时用户uuid生成的
//ByteSource salt = ByteSource.Util.bytes(user.getSalt());
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),this.getName());
return info;
}
return null;
}
/**
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
return null;
}
}
12.4 ShiroConfig
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(value = { SecurityManager.class })
@ConfigurationProperties(prefix = "shiro")
@Data
public class ShiroAutoConfiguration {
private static final String SHIRO_DIALECT = "shiroDialect";
private static final String SHIRO_FILTER = "shiroFilter";
// 加密方式
private String hashAlgorithmName = "md5";
// 散列次数
private int hashIterations = 2;
// 默认的登陆页面
private String loginUrl = "/index.html";
private String[] anonUrls; // 放行的路径
private String logOutUrl; // 登出的地址
private String[] authcUlrs; // 拦截的路径
/**
* 声明凭证匹配器
*/
/*@Bean("credentialsMatcher")
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName(hashAlgorithmName);
credentialsMatcher.setHashIterations(hashIterations);
return credentialsMatcher;
}*/
/**
* 声明userRealm
*/
@Bean("userRealm")
public UserRealm userRealm() {
UserRealm userRealm = new UserRealm();
return userRealm;
}
/**
* 配置SecurityManager
*/
@Bean("securityManager")
public SecurityManager securityManager(UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 注入userRealm
securityManager.setRealm(userRealm);
return securityManager;
}
/**
* 配置shiro的过滤器
*/
@Bean(SHIRO_FILTER)
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
// 设置安全管理器
factoryBean.setSecurityManager(securityManager);
// 设置未登陆的时要跳转的页面
factoryBean.setLoginUrl(loginUrl);
Map<String, String> filterChainDefinitionMap = new HashMap<>();
// 设置放行的路径
if (anonUrls != null && anonUrls.length > 0) {
for (String anon : anonUrls) {
filterChainDefinitionMap.put(anon, "anon");
System.out.println(anon);
}
}
// 设置登出的路径
if (null != logOutUrl) {
filterChainDefinitionMap.put(logOutUrl, "logout");
}
// 设置拦截的路径
if (authcUlrs != null && authcUlrs.length > 0) {
for (String authc : authcUlrs) {
filterChainDefinitionMap.put(authc, "authc");
}
}
Map<String, Filter> filters=new HashMap<>();
factoryBean.setFilters(filters);
factoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return factoryBean;
}
/**
* 注册shiro的委托过滤器,相当于之前在web.xml里面配置的
* @return
*/
@Bean
public FilterRegistrationBean<DelegatingFilterProxy> delegatingFilterProxy() {
FilterRegistrationBean<DelegatingFilterProxy> filterRegistrationBean = new FilterRegistrationBean<DelegatingFilterProxy>();
DelegatingFilterProxy proxy = new DelegatingFilterProxy();
proxy.setTargetFilterLifecycle(true);
proxy.setTargetBeanName(SHIRO_FILTER);
filterRegistrationBean.setFilter(proxy);
return filterRegistrationBean;
}
/* 加入注解的使用,不加入这个注解不生效--开始 */
/**
*
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
/* 加入注解的使用,不加入这个注解不生效--结束 */
/**
* 这里是为了能在html页面引用shiro标签,上面两个函数必须添加,不然会报错
*
* @return
*/
@Bean(name = SHIRO_DIALECT)
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}
12.5 static下的静态index.html跳转页面
<!--用来跳转-->
<script type="text/javascript">
window.location.href="/toLogin";
</script>
12.6 编写yml配置过滤路径
#shiro的配置
shiro:
anon-urls:
- /toLogin*
- /login.html*
- /login/login
- /login/getCode
- /css/**
- /echarts/**
- /images/**
- /layui/**
login-url: /index.html
log-out-url: /login/logout*
authc-ulrs:
- /**
十三、设计菜单、角色、班级、学院、老师等数据库设计
13.1 数据库设计
1.menu 菜单
id | pid | type | title | premission | icon | href | open | ordernum | available |
---|---|---|---|---|---|---|---|---|---|
1 | 0 | menu | 疫管理 | menu:select | /menu | 1 | 1 | 1 | |
2 | 1 | menu | 饼图 | menu:select | /pie | 0 | 2 | 1 |
2.role 角色
id | name | remark |
---|---|---|
1 | 超级管理员 | 拥有所有权限 |
2 | 老师 | 查看新增修改 |
3 | 学生 | 查看 |
3.role_menu 关联关系表
rid | mid |
---|---|
1 | 1 |
1 | 2 |
4.user 用户表【老师,学生,管理员】
id | username | password | … | role_id | ban_ji_id | xue_yuan_id | teacher_id |
---|---|---|---|---|---|---|---|
1 | admin | 123456 | 1 | 1 | 1 | 0 |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lYreSzPx-1654695993026)(C:\Users\15067\AppData\Local\Temp\1654489985017.png)]
5.ban_ji 班级表
id | name | xue_yuan_id |
---|---|---|
1 | 软件工程1班 | 1 |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-woDqyper-1654695993027)(C:\Users\15067\AppData\Local\Temp\1654490055131.png)]
6.xue_yuan学院表
id | name |
---|---|
1 | 计算机系 |
13.2 Java实体编写
十四、 菜单的增删改查
14.1 【dtree属性菜单和下拉实现查询所有】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pcP5hK4v-1654695993056)(C:\Users\15067\AppData\Local\Temp\1654494627502.png)]
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
14.1 菜单的插入
TreeNode
package com.example.demo.controller;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
/**
* @Author:
* @Date: 2019/11/22 15:25
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TreeNode {
private Integer id;
@JsonProperty("parentId") //返回的json的名称 parentId ,为了确定层级关系
private Integer pid;
private String title;
private String icon;
private String href;
private Boolean spread;
private List<TreeNode> children = new ArrayList<TreeNode>();
/**
* 0为不选中 1为选中
*/
private String checkArr="0";
/**
* 首页左边导航菜单的构造器
*/
public TreeNode(Integer id, Integer pid, String title, String icon, String href, Boolean spread) {
this.id = id;
this.pid = pid;
this.title = title;
this.icon = icon;
this.href = href;
this.spread = spread;
}
/**
* 部门 dtree的构造器
* @param id id
* @param pid 父亲parentId
* @param title 名称
* @param spread 是否展开
*/
public TreeNode(Integer id, Integer pid, String title, Boolean spread) {
this.id = id;
this.pid = pid;
this.title = title;
this.spread = spread;
}
/**
* 给角色分配权限的构造器
*/
public TreeNode(Integer id, Integer pid, String title, Boolean spread, String checkArr) {
this.id = id;
this.pid = pid;
this.title = title;
this.spread = spread;
this.checkArr = checkArr;
}
}
TreeBuilder
public class TreeNodeBuilder {
public static List<TreeNode> build(List<TreeNode> treeNodes, Integer topPid) {
List<TreeNode> nodes = new ArrayList<TreeNode>();
for (TreeNode n1 : treeNodes) {
if (n1.getPid()==topPid){
nodes.add(n1);
}
for (TreeNode n2 : treeNodes) {
if (n1.getId()==n2.getPid()){
n1.getChildren().add(n2);
}
}
}
return nodes;
}
}
14.2 dtree菜单下拉回显和展示所有
父级菜单ID为:0【必填】
14.3 菜单栏的编辑
menuService.updateById(menu);
14.4 菜单栏的删除
删除逻辑的时候:
@RequestMapping("/checkMenuHasChildrenNode")
@ResponseBody
public Map<String,Object> checkChildrenNode(Menu menu){
Map<String,Object> map = new HashMap<>();
QueryWrapper<Menu> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("pid",menu.getId());
List<Menu> list = menuService.list(queryWrapper);
if (list.size()>0){
map.put("value",true);
}else {
map.put("value",false);
}
return map;
}
1.子类ID,不能删除
2.没有子类ID,直接删掉
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J5zW7CMV-1654695993059)(C:\Users\15067\AppData\Local\Temp\1654516334725.png)]
真正的删除
@RequestMapping("/deleteMenu")
@ResponseBody
public DataView deleteMenu(Menu menu){
menuService.removeById(menu.getId());
DataView dataView = new DataView();
dataView.setCode(200);
dataView.setMsg("删除菜单成功!");
return dataView;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qw7zJ6wD-1654695993060)(C:\Users\15067\AppData\Local\Temp\1654521334106.png)]
14.5 修改index主菜单栏为动态查库
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9Hu3oVPi-1654695993062)(C:\Users\15067\AppData\Local\Temp\1654561883304.png)]
1.修改样式 引入 js css
2.配置yml放行js包
3.原项目修改index.html 为 china.html 删除 commonmenu.html 引入 静态资源包里面的 index.html
4.去掉其它页面 的 引入,添加
去掉
<!--layui公共模块-->
<div th:include="commonmenu :: menu"></div>
class="layui-body"
5.修改 indexcontroller 的请求/路径,添加一个/toChina
6.修改数据库 /toChina
7.编写Controller
/**
* 加载最外层index菜单
*/
@RequestMapping("loadIndexLeftMenuJson")
@ResponseBody
public DataView loadIndexLeftMenuJson(Menu permissionVo){
//查询所有菜单
QueryWrapper<Menu> queryWrapper = new QueryWrapper<>();
List<Menu> list = menuService.list();
List<TreeNode> treeNodes = new ArrayList<>();
for (Menu p : list) {
Integer id =p.getId();
Integer pid = p.getPid();
String title = p.getTitle();
String icon = p.getIcon();
String href = p.getHref();
Boolean spread = p.getOpen().equals(1)?true:false;
treeNodes.add(new TreeNode(id,pid,title,icon,href,spread));
}
//构造层级关系
List<TreeNode> list2 = TreeNodeBuilder.build(treeNodes,0);
return new DataView(list2);
}
十五、角色【管理员、学生、老师】CRUD,分配菜单权限
15.1 角色的增删改查【条件查询带有分页】
1.引入role的静态页面
页面进行菜单的增加
2…
3…
4…
15.2 为角色分配菜单权限
1.分配权限 menu【菜单的操作资源】id
2.分配角色 role【用户 管理员 学生 教师】id
3.关联表role_menu:【全都可以为空,不能有主键,都是外键属性】
rid mid
1 1
1 2
select mid from role_menu where rid = ?
List 所具有的菜单栏权限
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y6SmF4nA-1654695993063)(C:\Users\15067\AppData\Local\Temp\1654588068035.png)]
/**
* 1.初始化下拉列表的权限
*/
@Autowired
private MenuService menuService;
@RequestMapping("/initPermissionByRoleId")
@ResponseBody
public DataView initPermissionByRoleId(Integer roleId){
//查询所有菜单和权限
QueryWrapper<Menu> queryWrapper = new QueryWrapper<>();
List<Menu> allPermissions = menuService.list();
//1.首先根据角色id查询出当前角色所拥有的所有菜单的ID和权限的ID
List<Integer> currentRolePermissions = roleService.queryRolePermissionIdsByRid(roleId);
//2.根据查询出来的菜单ID和权限ID,再查询出菜单的数据和权限的数据
List<Menu> currentPermissions = null;
//如果根据角色id查询出来了菜单ID或权限ID,就去查询
if (currentRolePermissions.size()>0){
queryWrapper.in("id",currentRolePermissions);
currentPermissions = menuService.list(queryWrapper);
}else {
currentPermissions = new ArrayList<>();
}
//3.构造List<TreeNode>
List<TreeNode> nodes = new ArrayList<>();
for (Menu allPermission : allPermissions) {
String checkArr = "0";
for (Menu currentPermission : currentPermissions) {
if (allPermission.getId().equals(currentPermission.getId())){
checkArr = "1";
break;
}
}
Boolean spread = (allPermission.getOpen()==null||allPermission.getOpen()==1)?true:false;
nodes.add(new TreeNode(allPermission.getId(),allPermission.getPid(),allPermission.getTitle(),spread,checkArr));
}
return new DataView(nodes);
}
@Select("select mid from role_menu where rid = #{roleId}")
List<Integer> queryRolePermissionIdsByRid(Integer roleId);
分配菜单权限【角色与菜单之间的关系】
// 1.分配菜单栏之前删除所有的rid数据
@Delete("delete from role_menu where rid = #{rid}")
void deleteRoleByRid(Integer rid);
// 2.保存分配 角色 与 菜单 的关系
@Insert("insert into role_menu(rid,mid) values (#{rid},#{mid})")
void saveRoleMenu(Integer rid, Integer mid);
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vgPjN3si-1654695993065)(C:\Users\15067\AppData\Local\Temp\1654592413047.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jdsYmy5N-1654695993066)(C:\Users\15067\AppData\Local\Temp\1654592505578.png)]
十六、用户管理【增删改查、分配角色】
16.1 用户的增删改查,上传头像
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zjAy90zy-1654695993069)(C:\Users\15067\AppData\Local\Temp\1654602632710.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1X3YI2DT-1654695993070)(C:\Users\15067\AppData\Local\Temp\1654602643229.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2uuKQyTQ-1654695993072)(C:\Users\15067\AppData\Local\Temp\1654602658000.png)]
1.引入以页面
2.编写代码
查询所有带有分页 带有查询条件
第一种办法:如何连表查询????????
自定义方法:
// 1.第一种办法
//if (StringUtils.isNotBlank(userVo.getUsername())){
// userService.loadUserByLeftJoin(userVo.getUsername(),userVo.getPage(),userVo.getLimit());
//}
// 2.mapper
//@Select("select a.username,b.name FROM user as a where a.username = #{} LEFT JOIN ban_ji as b ON a.ban_ji_id = b.id limit #{},#{}")
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S8OVKZ3o-1654695993074)(C:\Users\15067\AppData\Local\Temp\1654658187253.png)]
sql:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X3mBpmB0-1654695993076)(C:\Users\15067\AppData\Local\Temp\1654658432315.png)]
// 2.第二种办法
1.ipage【User所有数据】—> banjiID ----->ban_ji 表 名字给ipage对象进行赋值
2.添加属性
// 非数据库列 班级名字
@TableField(exist = false)
private String banJiName;
// 非数据库列 学院名字
@TableField(exist = false)
private String xueYuanName;
// 非数据库列 老师名字
@TableField(exist = false)
private String teacherName;
// 2.第二种办法
// 查到所有的数据 1 1 1
for (User user : iPage.getRecords()){
// 为班级名字进行赋值
if (user.getBanJiId()!=null){
// 班级banJiService查库
BanJi banji = banJiService.getById(user.getBanJiId());
user.setBanJiName(banji.getName());
}
// 为学院名字进行赋值
if (user.getXueYuanId()!=null){
XueYuan xueYuan = xueYuanService.getById(user.getXueYuanId());
user.setXueYuanName(xueYuan.getName());
}
// 为老师名字进行赋值
if (user.getTeacherId()!=null){
User teacher = userService.getById(user.getTeacherId());
user.setTeacherName(teacher.getUsername());
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OO3Jte47-1654695993079)(C:\Users\15067\AppData\Local\Temp\1654660307518.png)]
16.2 用户新增或编辑和删除
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BafuoUAp-1654695993081)(C:\Users\15067\AppData\Local\Temp\1654665124918.png)]
填充数据【下拉列表】
//初始化下拉列表【班级】
$.get("/user/listAllBanJi",function (res) {
var banji = res;
var dom_banji=$("#banji");
var html = "<option value=''>选择班级</option>";
$.each(banji,function (index,item) {
html+="<option value='"+item.id+"'>"+item.name+"</option>";
});
dom_banji.html(html);
form.render("select");
})
//初始化下拉列表【学院】
$.get("/user/listAllXueYuan",function (res) {
var xueyuan = res;
var dom_xueyuan=$("#xueyuan");
var html = "<option value=''>选择学院</option>";
$.each(xueyuan,function (index,item) {
html+="<option value='"+item.id+"'>"+item.name+"</option>";
});
dom_xueyuan.html(html);
form.render("select");
})
/**
* 初始化下拉列表的数据【班级】
*/
@RequestMapping("/listAllBanJi")
@ResponseBody
public List<BanJi> listAllBanJi(){
List<BanJi> list = banJiService.list();
return list;
}
/**
* 初始化下拉列表的数据【学院】
*/
@RequestMapping("/listAllXueYuan")
@ResponseBody
public List<XueYuan> listAllXueYuan(){
List<XueYuan> list = xueYuanService.list();
return list;
}
熙增编辑和删除
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ru7dPd9X-1654695993083)(C:\Users\15067\AppData\Local\Temp\1654669217432.png)]
16.2 重置密码、修改密码
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ibuaAoAm-1654695993084)(C:\Users\15067\AppData\Local\Temp\1654669197299.png)]
16.3 给用户分配角色【一个用户可能多重角色 1:m】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k5bWuzmb-1654695993087)(C:\Users\15067\AppData\Local\Temp\1654680325599.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FUVKTwBT-1654695993090)(C:\Users\15067\AppData\Local\Temp\1654680700125.png)]
1.创建一张角色与用户的维护表 user_role
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sihWT4gD-1654695993092)(C:\Users\15067\AppData\Local\Temp\1654680810232.png)]
2.初始化点击的角色列表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CI3qBSkO-1654695993094)(C:\Users\15067\AppData\Local\Temp\1654682183307.png)]
//controller
@RequestMapping("/initRoleByUserId")
@ResponseBody
public DataView initRoleByUserId(Integer id){
// 1.查询所有角色
List<Map<String, Object>> listMaps = roleService.listMaps();
// 2.查询当前登录用户所拥有的角色
List<Integer> currentUserRoleIds = roleService.queryUserRoleById(id);
// 3.让你的前端 变为选中状态
for (Map<String,Object> map : listMaps){
Boolean LAY_CHECKED = false;
Integer roleId = (Integer) map.get("id");
for (Integer rid : currentUserRoleIds){
if (rid.equals(roleId)){
LAY_CHECKED = true;
break;
}
}
map.put("LAY_CHECKED",LAY_CHECKED);
}
return new DataView(Long.valueOf(listMaps.size()),listMaps);
}
// mapper 根据用户id查询所有的角色
@Select("select rid from user_role where uid = #{id}")
List<Integer> queryUserRoleById(Integer id);
3.确认分配角色保存角色分配关系
1.删除之前的用户与角色关系
2.保存用户与角色的关系
public void saveUserRole(Integer uid, Integer[] ids) {
roleMapper.deleteRoleUserByUid(uid);
if (ids!=null&&ids.length>0){
for (Integer rid : ids){
roleMapper.saveUserRole(uid,rid);
}
}
}
// 1. 先删除之前的用户与角色关系
@Delete("delete from user_role where uid = #{uid}")
void deleteRoleUserByUid(Integer uid);
//2. 保存分配的用户与角色之间的关系
@Insert("insert into user_role(uid,rid) values(#{uid},#{rid})")
void saveUserRole(Integer uid, Integer rid);
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n12IcJFS-1654695993097)(C:\Users\15067\AppData\Local\Temp\1654683042820.png)]
十七、不同的用户【角色】登录看到不同的菜单栏
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GXz3C9AZ-1654695993099)(C:\Users\15067\AppData\Local\Temp\1654689887283.png)]
用户 : 角色 : 菜单
id ---- List
role — List
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XdvWj4X3-1654695993105)(C:\Users\15067\AppData\Local\Temp\1654690134405.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-owaLSutJ-1654695993106)(C:\Users\15067\AppData\Local\Temp\1654690116614.png)]
加载左侧主页菜单栏的时候进行条件查询【OK】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H1WaOr6R-1654695993107)(C:\Users\15067\AppData\Local\Temp\1654691392426.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ceeVsGiU-1654695993108)(C:\Users\15067\AppData\Local\Temp\1654691492269.png)]
不同角色展现不同的菜单实现逻辑
// 查询的所有菜单栏 按照条件查询【管理员,学生 老师【条件查询】】
QueryWrapper<Menu> queryWrapper = new QueryWrapper<>();
List<Menu> list = null;
// 1.取出session中的用户ID
User user = (User) session.getAttribute("user");
Integer userId = user.getId();
// 2.根据用户ID查询角色ID
List<Integer> currentUserRoleIds = roleService.queryUserRoleById(userId);
// 3.去重
Set<Integer> mids = new HashSet<>();
for (Integer rid : currentUserRoleIds){
// 3.1.根据角色ID查询菜单ID
List<Integer> permissionIds = roleService.queryAllPermissionByRid(rid);
// 3.2.菜单栏ID和角色ID去重
mids.addAll(permissionIds);
}
// 4.根据角色ID查询菜单ID
if (mids.size()>0){
queryWrapper.in("id",mids);
list = menuService.list(queryWrapper);
}