该项目基于springboot框架实现了疫情背景下的校园管理,主要涵盖了中国疫情确诊分布地图(对接腾讯API接口)、中国实时疫情新闻播报、以及对疫情数据的饼图、折线图和柱状图展示。系统角色可以进行增删改查,为角色分配菜单权限,大致分为学生、教师、院系级系统管理员,集成了shiro框架实现了不同的角色可以赋予不同的菜单栏权限,可以完成菜单栏的动态增删改查,实现了动态的权限设置。
在校园疫情数据管理中,基于mybatiplus框架实现了疫情数据的带有条件查询及分页的增删改查,并实现了拖拽式上传excel数据和导出疫情数据。其中相关功能主要包含疫情数据管理,疫情新闻管理,疫情风险地区查询,近30日疫情新增趋势等等,疫情图表展示管理,学生健康打卡管理,院系管理、班级管理、核酸检测管理,疫苗接种管理,学生请假管理等等。系统管理中主要包含了用户管理、角色管理和菜单管理。
在数据库设计中,主要设计了用户与角色之间的多对多实体关系,角色与菜单之间的多对多关系设计。其中,学生请假功能设计了详细的审批流,学生提交审批流后,教师审批后方为院系审批,为串行审批流程,设计了审批节点的状态与审批流的审批逻辑。
在缓存设计中,主要将访问量较高的中国疫情地图和新闻播报首页进行了redis数据缓存,为了保证redis缓存与mysql数据的一致性,每当定时任务触发去解析腾讯API接口数据时候,每更新一次数据库数据,就要删除一次redis缓存,这样就可以保证客户端每次查询数据查不到就可以进行访问数据库更新最新的缓存数据。
在接口设计中,采用了RestFul风格的架构,使请求路径变得更加简洁,传递、获取参数值更加方便,通过请求路径中直接传递参数值,不会暴露传递给方法的参数变量名,接口也变得更加安全。
【Coding路人王:从0到1】
上课地址:https://www.bilibili.com/video/BV1aY411c7d1?share_source=copy_web
源码下载:源码下载:https://download.csdn.net/download/wyn_365/87097570
已完成展示
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.1.1version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.47version>
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.官网:https://echarts.apache.org/zh/ 下载JS文件引入项目
2.查看图例
3.快速使用
DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script src="echarts.js">script>
head>
html>
DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>EChartstitle>
<script src="echarts.js">script>
head>
<body>
<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/
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');
springboot
contRoller: /query
service:
dao:
entity:
$.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
}
]
});
}
});
@Configuration
@ConditionalOnClass(value = {PaginationInterceptor.class})
public class MybatisPlusConfig {
@Bean
public PaginationInterceptor paginationInterceptor(){
return new PaginationInterceptor();
}
}
@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;
}
}
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poiartifactId>
<version>4.0.0version>
dependency>
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poi-ooxmlartifactId>
<version>4.0.0version>
dependency>
* 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);
}
});
// 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;
}
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();
}
}
}
**主页网址:**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
<dependency>
<groupId>org.apache.httpcomponentsgroupId>
<artifactId>httpclientartifactId>
<version>4.5.2version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.56version>
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();
}
}
}
}
@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);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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 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);
前台: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 字符串
登录:认证,,授权
认证:登录,学生—》学校 大门口进门
**授权:**学生=男生 【男生宿舍 男生厕所】
role【学生 老师 管理员】
学生:查看
老师:修改
管理员:删除
1.先要判断验证码对不对?
2.username password SQL
3.SHIRO 权限【角色】
吃饭时候:跑外面 砍一棵大树 一双筷子
使用。
<dependency>
<groupId>cn.hutoolgroupId>
<artifactId>hutool-allartifactId>
<version>4.6.8version>
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);
}
}
登陆之前:除了登录页面和静态资源之外全部拦截掉
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,即安全数据源。
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-springartifactId>
<version>1.4.2version>
dependency>
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-coreartifactId>
<version>1.4.2version>
dependency>
<dependency>
<groupId>com.github.theborakompanionigroupId>
<artifactId>thymeleaf-extras-shiroartifactId>
<version>2.0.0version>
dependency>
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;
}
}
@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();
}
}
#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:
- /**
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 | 计算机系 |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pcP5hK4v-1654695993056)(C:\Users\15067\AppData\Local\Temp\1654494627502.png)]
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-lang3artifactId>
dependency>
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;
}
}
父级菜单ID为:0【必填】
menuService.updateById(menu);
删除逻辑的时候:
@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)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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.去掉其它页面 的 引入,添加
去掉
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);
}
1.引入role的静态页面
页面进行菜单的增加
2…
3…
4…
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
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)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ibuaAoAm-1654695993084)(C:\Users\15067\AppData\Local\Temp\1654669197299.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k5bWuzmb-1654695993087)(C:\Users\15067\AppData\Local\Temp\1654680325599.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FUVKTwBT-1654695993090)(C:\Users\15067\AppData\Local\Temp\1654680700125.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sihWT4gD-1654695993092)(C:\Users\15067\AppData\Local\Temp\1654680810232.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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);
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);
}