# yyzx
**Repository Path**: hrbu-2022/yyzx
## Basic Information
- **Project Name**: yyzx
- **Description**: No description available
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 1
- **Created**: 2025-03-25
- **Last Updated**: 2025-04-27
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 初始化 前后端分离的工程

## 创建一个Vue项目
使用的JavaScript
npm create vue@latest

## 启动测试


## 配置插件
```
npm i vite-plugin-vue-setup-extend -D
```

# 添加工作区 和登录组件
- 调整main.css样式
- 编写home.vue
- login.vue
- 配置路由


### main.css
```
body,h1,h2{
margin: 0;
padding: 0;
}
```
### Vue文件
login.vue
```vue
登录组件
```
home.vue
```vue
登录之后的工作区
```
### 配置路由
yyzx-vue/src/router/index.js
```js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView,
},
{
path: '/login',
name: 'login',
component: () => import('../views/Login.vue'),
},
],
})
export default router
```
# 配置框架页面
### 安装使用ElementPlus
https://element-plus.org/zh-CN/guide/quickstart.html
```
npm install element-plus --save
```

### 配置
配置main.js

### 修改HomeView.vue
参考
https://element-plus.org/zh-CN/component/container.html
```vue
Aside
Main
```

# 动态菜单
http://localhost:5173/user
http://localhost:5173/menu
http://localhost:5173/role
### 准备三个组件
- /permission/user/index.vue
- /permission/role/index.vue
- /permission/meun/index.vue
### 配置路由
```js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView,
children:[
{
path: '/user',
name: 'user',
component: () => import('../views/permission/user/index.vue'),
},
{
path: '/role',
name: 'role',
component: () => import('../views/permission/role/index.vue'),
},
{
path: '/menu',
name: 'menu',
component: () => import('../views/permission/menu/index.vue'),
},
]
},
{
path: '/login',
name: 'login',
component: () => import('../views/Login.vue'),
},
],
})
export default router
```
### 测试



# 整合菜单
nav-menu
## 定义菜单组件
src\components\NavMenu.vue
```vue
```
## 修改 HomeView.vue aside部分的代码

# 使用ElementPlus图标
https://element-plus.org/zh-CN/component/icon.html
- 需要安装
- 在某一个需要使用的位置 设置图标库
## 安装
```
npm install @element-plus/icons-vue
```

## 注册图标
您需要从 `@element-plus/icons-vue` 中导入所有图标并进行全局注册。、
在main.js中执行下面的操作

### 使用
```
```
具体图标请参考
https://element-plus.org/zh-CN/component/icon.html#icon-collection
# 实现CRUD的 视图效果(静态内容)
参考:
演示效果 https://vue.ruoyi.vip/login?redirect=%2Findex、
代码: https://gitee.com/y_project/RuoYi-Vue
实现了一个 用户列表的功能
```vue
搜索
新增
```
# 搭建 springboot+mybaits
- spirngboot
- 整合mybaits
- 封装统一的返回结果

## Boot(SrpingBoot)
- 添加父项目
- 添加依赖
```xml
4.0.0
org.springframework.boot
spring-boot-starter-parent
3.3.9
com.neuedu.yyzx
yyzx-server
1.0-SNAPSHOT
17
17
3.5.10.1
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
org.projectlombok
lombok
com.baomidou
mybatis-plus-spring-boot3-starter
${mp.version}
com.baomidou
mybatis-plus-jsqlparser
${mp.version}
com.mysql
mysql-connector-j
```
### 创建基础包
com.neuedu.yyzx
### 创建启动main函数
```java
package com.neuedu.yyzx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author 金山
* 项目:yyzx
* site: https://blog.fulfill.com.cn
* 描述 启动类
* @data 2025/3/2514:03
*/
@SpringBootApplication
public class YyzxApp {
public static void main(String[] args) {
SpringApplication.run(YyzxApp.class ,args);
}
}
```
### 编写controller并测试
```java
package com.neuedu.yyzx.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author 金山
* 项目:yyzx
* site: https://blog.fulfill.com.cn
* 描述
* @data 2025/3/2514:04
*/
@RestController
public class IndexController {
@RequestMapping("/")
String index(){
return "success";
}
}
```


# 整合MP
## 准备数据库
````sql
/*
SQLyog Ultimate v12.08 (64 bit)
MySQL - 8.0.30 : Database - yyzx
*********************************************************************
*/
/*!40101 SET NAMES utf8 */;
/*!40101 SET SQL_MODE=''*/;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`yyzx` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;
USE `yyzx`;
/*Table structure for table `ums_menu` */
DROP TABLE IF EXISTS `ums_menu`;
CREATE TABLE `ums_menu` (
`menu_id` bigint NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
`menu_name` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '菜单名称',
`url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '前端菜单路径',
`path` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '前端vue-router路径',
`level` int DEFAULT NULL COMMENT '菜单的级别',
`parent_id` bigint DEFAULT '0' COMMENT '上级ID',
`show` int NOT NULL DEFAULT '0' COMMENT '是否显示,0 显示,1 不显示',
`del` varchar(5) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT '1' COMMENT '是否有效',
`createtime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`menu_id`)
) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='菜单表(权限)';
/*Data for the table `ums_menu` */
insert into `ums_menu`(`menu_id`,`menu_name`,`url`,`path`,`level`,`parent_id`,`show`,`del`,`createtime`) values (1,'系统管理','xtgl',NULL,1,0,0,'1','2024-11-25 15:13:36'),(2,'基础信息管理','jcxxgl',NULL,NULL,0,0,'1','2024-11-25 15:16:56'),(3,'用户管理','/home/user','ums/user/index.vue',2,1,0,'1','2024-11-25 15:20:17'),(4,'部门管理','/home/dept','base/dept/index.vue',4,2,0,'1','2024-11-25 15:26:11'),(6,'挂号级别','/home/regist_level','base/regist_level/index.vue',2,2,0,'1','2024-11-25 16:08:16'),(7,'常数项类型管理','/home/constants_type','base/constants_type/index.vue',2,2,0,'1','2024-11-25 16:09:45'),(8,'角色管理','/home/role','ums/role/index.vue',2,1,0,'1','2024-11-25 16:10:11'),(9,'菜单管理','/home/menu','ums/menu/index.vue',1,1,0,'1','2024-11-25 16:10:23'),(10,'挂号收费员','ghsfy',NULL,1,0,0,'1','2024-11-28 08:46:54'),(11,'挂号','/home/guahao','guahao/guahao/index.vue',2,10,0,'1','2024-11-28 08:47:14'),(12,'收费','/home/fee','guahao/fee/index.vue',2,10,0,'1','2024-11-28 08:47:28'),(13,'退号','/home/refund_regist','guahao/refund-regist/index.vue',2,10,0,'1','2024-11-28 08:47:49'),(14,'退费','/home/refund','guahao/refund/index.vue',2,10,0,'1','2024-11-28 08:48:01'),(15,'门诊医生','mzys',NULL,1,0,0,'1','2024-11-28 08:48:21'),(16,'门诊病历','/home/doctor','doctor/index.vue',2,15,0,'1','2024-11-28 08:48:33'),(17,'医技医生','yjys','12121212121112',1,0,0,'1','2024-11-28 08:48:59'),(18,'检查处理','/home/checkapply','skill/checkapply/index.vue',2,17,0,'1','2024-11-28 08:49:12'),(19,'检验处理','/home/inspectapply','skill/inspectapply/index.vue',2,17,0,'1','2024-11-28 08:49:30'),(25,'常数项目','/home/constants_item','base/constants_item/index.vue',2,2,1,'1','2024-11-29 08:35:55'),(28,'检查项目管理','/home/checkitem','base/checkitem/index.vue',2,2,0,'1','2024-12-02 13:38:00'),(29,'检验项目管理','/home/inspect','base/inspectitem/index.vue',2,2,0,'1','2024-12-02 13:38:56'),(30,'检查项目列表','/home/checkapplylist','skill/checkapply/check_apply_list.vue',2,17,1,'1','2024-12-05 09:01:02');
/*Table structure for table `ums_role` */
DROP TABLE IF EXISTS `ums_role`;
CREATE TABLE `ums_role` (
`role_id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色ID',
`role_name` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '角色名称',
`del` varchar(5) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT '1' COMMENT '是否有效',
`createtime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='角色';
/*Data for the table `ums_role` */
insert into `ums_role`(`role_id`,`role_name`,`del`,`createtime`) values (1,'系统管理员','1','2024-11-25 14:43:37'),(2,'挂号收费员','1','2024-11-25 14:45:05'),(3,'门诊医生','1','2024-11-25 14:45:17'),(5,'医技医生','1','2024-11-25 14:46:23'),(10,'121122','1','2024-11-26 12:25:52');
/*Table structure for table `ums_user` */
DROP TABLE IF EXISTS `ums_user`;
CREATE TABLE `ums_user` (
`userid` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '用户名',
`password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '密码',
`nickname` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '昵称',
`expire` date DEFAULT NULL COMMENT '过期时间',
`lastlogin` datetime DEFAULT NULL COMMENT '最后一次登录时间',
`del` varchar(5) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT '1' COMMENT '是否有效',
`createtime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`userid`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='医生、护士、医技等用户';
/*Data for the table `ums_user` */
insert into `ums_user`(`userid`,`username`,`password`,`nickname`,`expire`,`lastlogin`,`del`,`createtime`) values (1,'admin','abc123456789','系统管理员',NULL,'2024-12-06 15:06:20',NULL,'2024-11-21 14:21:01'),(3,'ssm','123456','孙思邈','2025-04-26','2024-12-06 13:56:59',NULL,'2024-11-22 13:51:33'),(7,'ht','123456','华佗',NULL,'2024-12-03 12:24:56',NULL,'2024-11-25 13:43:59'),(8,'guahao','123456','挂号员','2024-11-29','2024-12-06 11:34:18','1','2024-11-28 08:53:37'),(9,'yiji','123456','霍华德',NULL,'2024-12-06 11:53:09','1','2024-11-28 08:54:20'),(10,NULL,NULL,NULL,NULL,NULL,'1','2024-11-28 09:26:02');
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
/*
SQLyog Ultimate v12.08 (64 bit)
MySQL - 8.0.30 : Database - yyzx
*********************************************************************
*/
/*!40101 SET NAMES utf8 */;
/*!40101 SET SQL_MODE=''*/;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`yyzx` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;
USE `yyzx`;
/*Table structure for table `ums_menu` */
DROP TABLE IF EXISTS `ums_menu`;
CREATE TABLE `ums_menu` (
`menu_id` bigint NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
`menu_name` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '菜单名称',
`url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '前端菜单路径',
`path` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '前端vue-router路径',
`level` int DEFAULT NULL COMMENT '菜单的级别',
`parent_id` bigint DEFAULT '0' COMMENT '上级ID',
`show` int NOT NULL DEFAULT '0' COMMENT '是否显示,0 显示,1 不显示',
`del` varchar(5) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT '1' COMMENT '是否有效',
`createtime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`menu_id`)
) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='菜单表(权限)';
/*Data for the table `ums_menu` */
insert into `ums_menu`(`menu_id`,`menu_name`,`url`,`path`,`level`,`parent_id`,`show`,`del`,`createtime`) values (1,'系统管理','xtgl',NULL,1,0,0,'1','2024-11-25 15:13:36'),(2,'基础信息管理','jcxxgl',NULL,NULL,0,0,'1','2024-11-25 15:16:56'),(3,'用户管理','/home/user','ums/user/index.vue',2,1,0,'1','2024-11-25 15:20:17'),(4,'部门管理','/home/dept','base/dept/index.vue',4,2,0,'1','2024-11-25 15:26:11'),(6,'挂号级别','/home/regist_level','base/regist_level/index.vue',2,2,0,'1','2024-11-25 16:08:16'),(7,'常数项类型管理','/home/constants_type','base/constants_type/index.vue',2,2,0,'1','2024-11-25 16:09:45'),(8,'角色管理','/home/role','ums/role/index.vue',2,1,0,'1','2024-11-25 16:10:11'),(9,'菜单管理','/home/menu','ums/menu/index.vue',1,1,0,'1','2024-11-25 16:10:23'),(10,'挂号收费员','ghsfy',NULL,1,0,0,'1','2024-11-28 08:46:54'),(11,'挂号','/home/guahao','guahao/guahao/index.vue',2,10,0,'1','2024-11-28 08:47:14'),(12,'收费','/home/fee','guahao/fee/index.vue',2,10,0,'1','2024-11-28 08:47:28'),(13,'退号','/home/refund_regist','guahao/refund-regist/index.vue',2,10,0,'1','2024-11-28 08:47:49'),(14,'退费','/home/refund','guahao/refund/index.vue',2,10,0,'1','2024-11-28 08:48:01'),(15,'门诊医生','mzys',NULL,1,0,0,'1','2024-11-28 08:48:21'),(16,'门诊病历','/home/doctor','doctor/index.vue',2,15,0,'1','2024-11-28 08:48:33'),(17,'医技医生','yjys','12121212121112',1,0,0,'1','2024-11-28 08:48:59'),(18,'检查处理','/home/checkapply','skill/checkapply/index.vue',2,17,0,'1','2024-11-28 08:49:12'),(19,'检验处理','/home/inspectapply','skill/inspectapply/index.vue',2,17,0,'1','2024-11-28 08:49:30'),(25,'常数项目','/home/constants_item','base/constants_item/index.vue',2,2,1,'1','2024-11-29 08:35:55'),(28,'检查项目管理','/home/checkitem','base/checkitem/index.vue',2,2,0,'1','2024-12-02 13:38:00'),(29,'检验项目管理','/home/inspect','base/inspectitem/index.vue',2,2,0,'1','2024-12-02 13:38:56'),(30,'检查项目列表','/home/checkapplylist','skill/checkapply/check_apply_list.vue',2,17,1,'1','2024-12-05 09:01:02');
/*Table structure for table `ums_role` */
DROP TABLE IF EXISTS `ums_role`;
CREATE TABLE `ums_role` (
`role_id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色ID',
`role_name` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '角色名称',
`del` varchar(5) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT '1' COMMENT '是否有效',
`createtime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='角色';
/*Data for the table `ums_role` */
insert into `ums_role`(`role_id`,`role_name`,`del`,`createtime`) values (1,'系统管理员','1','2024-11-25 14:43:37'),(2,'挂号收费员','1','2024-11-25 14:45:05'),(3,'门诊医生','1','2024-11-25 14:45:17'),(5,'医技医生','1','2024-11-25 14:46:23'),(10,'121122','1','2024-11-26 12:25:52');
/*Table structure for table `ums_user` */
DROP TABLE IF EXISTS `ums_user`;
CREATE TABLE `ums_user` (
`userid` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '用户名',
`password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '密码',
`nickname` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '昵称',
`expire` date DEFAULT NULL COMMENT '过期时间',
`lastlogin` datetime DEFAULT NULL COMMENT '最后一次登录时间',
`del` varchar(5) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT '1' COMMENT '是否有效',
`createtime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`userid`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='医生、护士、医技等用户';
/*Data for the table `ums_user` */
insert into `ums_user`(`userid`,`username`,`password`,`nickname`,`expire`,`lastlogin`,`del`,`createtime`) values (1,'admin','abc123456789','系统管理员',NULL,'2024-12-06 15:06:20',NULL,'2024-11-21 14:21:01'),(3,'ssm','123456','孙思邈','2025-04-26','2024-12-06 13:56:59',NULL,'2024-11-22 13:51:33'),(7,'ht','123456','华佗',NULL,'2024-12-03 12:24:56',NULL,'2024-11-25 13:43:59'),(8,'guahao','123456','挂号员','2024-11-29','2024-12-06 11:34:18','1','2024-11-28 08:53:37'),(9,'yiji','123456','霍华德',NULL,'2024-12-06 11:53:09','1','2024-11-28 08:54:20'),(10,NULL,NULL,NULL,NULL,NULL,'1','2024-11-28 09:26:02');
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
````
## 生成mp的代码



## 注册Mapper到IOC容器中
在启动类上添加 @MapperScan

## 配置数据源
```
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/yyzx?characterEncoding=utf-8
username: root
password: root
```
## 测试Mapper
```java
package com.neuedu.yyzx.mapper;
import com.neuedu.yyzx.po.UmsUser;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
/**
* @author 金山
* 项目:yyzx
* site: https://blog.fulfill.com.cn
* 描述
* @data 2025/3/2514:20
*/
@SpringBootTest
class UmsUserMapperTest {
@Autowired
private UmsUserMapper userMapper;
@Test
public void test(){
List umsUsers = userMapper.selectList(null);
System.out.println("umsUsers.size() = " + umsUsers.size());
}
}
```

# 编写用户列表的接口
编写后端程序用于提供 查询用户列表并返回json数据 给前台
## 编写UserController
```java
package com.neuedu.yyzx.controller;
import com.neuedu.yyzx.po.UmsUser;
import com.neuedu.yyzx.service.UmsUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @author 金山
* 项目:yyzx
* site: https://blog.fulfill.com.cn
* 描述
* @data 2025/3/2514:23
*/
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UmsUserService userService;
/**
* http://localhost:8080/user/list
* @return
*/
@GetMapping("/list")
public List list(){
return userService.list();
}
}
```
## 测试
在地址栏中直接访问
http://localhost:8080/user/list

# 前端使用接口
使用axios的工具
## 安装axios
```shell
npm install axios
```

## 使用axios
```vue
搜索
新增
```
发现报错了

## 解决跨域
[使用springboot(SpringMVC)的方案解决跨域](https://docs.spring.io/spring-framework/docs/5.3.39/reference/html/web.html#spring-web)
在IOC中添加一个WebMvcConfigurer的实现
```java
package com.neuedu.yyzx.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
/**
* 设置允许跨域
* @param registry
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/*/**")
.allowedOrigins("*")
.allowedMethods("*")
.maxAge(3600);
// Add more mappings...
}
}
```

修改获取返回值
````vue
搜索
新增
````
# 统一的异常处理
编写controller
```java
@GetMapping("/div/{numa}/{numb}")
public int div(@PathVariable int numa,@PathVariable int numb){
return numa / numb;
}
```
状态
正常的
200
失败的
500
403
4 01
400


原生的axios的响应对象

## 使用通用的返回结果

# 前端封装axios
utils/request.js
```js
import axios from 'axios'
// import { Notification, MessageBox, Message, Loading } from 'element-ui'
// import store from '@/store'
// import { getToken } from '@/utils/auth'
// import errorCode from '@/utils/errorCode'
// import { tansParams, blobValidate } from "@/utils/ruoyi";
// import cache from '@/plugins/cache'
// import { saveAs } from 'file-saver'
let downloadLoadingInstance;
// 是否显示重新登录
export let isRelogin = { show: false };
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 创建axios实例
const service = axios.create({
// axios中请求配置有baseURL选项,表示请求URL公共部分
// baseURL: process.env.VUE_APP_BASE_API,
baseURL: "http://localhost:8080",
// 超时
timeout: 10000
})
// request拦截器
service.interceptors.request.use(config => {
// 是否需要设置 token
// const isToken = (config.headers || {}).isToken === false
// // 是否需要防止数据重复提交
// const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
// if (getToken() && !isToken) {
// config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
// }
// get请求映射params参数
// if (config.method === 'get' && config.params) {
// let url = config.url + '?' + tansParams(config.params);
// url = url.slice(0, -1);
// config.params = {};
// config.url = url;
// }
// if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
// const requestObj = {
// url: config.url,
// data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
// time: new Date().getTime()
// }
// const requestSize = Object.keys(JSON.stringify(requestObj)).length; // 请求数据大小
// const limitSize = 5 * 1024 * 1024; // 限制存放数据5M
// if (requestSize >= limitSize) {
// console.warn(`[${config.url}]: ` + '请求数据大小超出允许的5M限制,无法进行防重复提交验证。')
// return config;
// }
// const sessionObj = cache.session.getJSON('sessionObj')
// if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
// cache.session.setJSON('sessionObj', requestObj)
// } else {
// const s_url = sessionObj.url; // 请求地址
// const s_data = sessionObj.data; // 请求数据
// const s_time = sessionObj.time; // 请求时间
// const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交
// if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {
// const message = '数据正在处理,请勿重复提交';
// console.warn(`[${s_url}]: ` + message)
// return Promise.reject(new Error(message))
// } else {
// cache.session.setJSON('sessionObj', requestObj)
// }
// }
// }
return config
}, error => {
console.log(error)
Promise.reject(error)
})
// 响应拦截器
service.interceptors.response.use(res => {
// 未设置状态码则默认成功状态
const code = res.data.code || 200;
// 获取错误信息
// const msg = errorCode[code] || res.data.msg || errorCode['default']
const msg = res.data.msg || '系统铸错了'
// 二进制数据则直接返回
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
return res.data
}
if (code === 401) {
// if (!isRelogin.show) {
// isRelogin.show = true;
// MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => {
// isRelogin.show = false;
// store.dispatch('LogOut').then(() => {
// location.href = '/index';
// })
// }).catch(() => {
// isRelogin.show = false;
// });
// }
return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
} else if (code === 500) {
alert('失败了...');
// Message({ message: msg, type: 'error' })
return Promise.reject(new Error(msg))
} else if (code === 601) {
alert('失败了...');
// Message({ message: msg, type: 'warning' })
return Promise.reject('error')
} else if (code !== 200) {
// Notification.error({ title: msg })
alert('失败了...');
return Promise.reject('error')
} else {
return res.data.data
}
},
error => {
console.log('err' + error)
let { message } = error;
if (message == "Network Error") {
message = "后端接口连接异常";
} else if (message.includes("timeout")) {
message = "系统接口请求超时";
} else if (message.includes("Request failed with status code")) {
message = "系统接口" + message.substr(message.length - 3) + "异常";
}
alert('失败了...');
// Message({ message: message, type: 'error', duration: 5 * 1000 })
return Promise.reject(error)
}
)
// 通用下载方法
export function download(url, params, filename, config) {
downloadLoadingInstance = Loading.service({ text: "正在下载数据,请稍候", spinner: "el-icon-loading", background: "rgba(0, 0, 0, 0.7)", })
return service.post(url, params, {
transformRequest: [(params) => { return tansParams(params) }],
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
responseType: 'blob',
...config
}).then(async (data) => {
const isBlob = blobValidate(data);
if (isBlob) {
const blob = new Blob([data])
saveAs(blob, filename)
} else {
const resText = await data.text();
const rspObj = JSON.parse(resText);
const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']
// Message.error(errMsg);
}
downloadLoadingInstance.close();
}).catch((r) => {
console.error(r)
// Message.error('下载文件出现错误,请联系管理员!')
downloadLoadingInstance.close();
})
}
export default service
```
使用前端API调用request.js

# 使用ElementPlus提供的通知、消息
```js
import { ElMessageBox, ElNotification } from 'element-plus'
ElNotification({
title: '消息',
type: 'error',
message: msg,
})
```
## 添加分页查询
配置分页插件
```java
package com.neuedu.yyzx.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author 金山
* 项目:yyzx
* site: https://blog.fulfill.com.cn
* 描述
* @data 2025/3/2610:58
*/
@Configuration
public class MyBaitsPlusConfig {
/**
* MP的插件系统
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//注册 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
//乐观锁的插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
```
分页方法
```java
@GetMapping("/page")
public Ret > page(Page page){
return Ret.ok(userService.page(page));
}
```
# ElementPlus国际化
https://element-plus.org/zh-CN/guide/i18n.html


# 前端的await 和 async
[Promise阅读网站](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise)
一个 **`Promise`** 是一个代理,它代表一个在创建 promise
时不一定已知的值。它允许你将处理程序与异步操作的最终成功值或失败原因关联起来。这使得异步方法可以像同步方法一样返回值:异步方法不会立即返回最终值,而是返回一个 *promise*,以便在将来的某个时间点提供该值。
一个 `Promise` 必然处于以下几种状态之一:
- *待定(pending)*:初始状态,既没有被兑现,也没有被拒绝。
- *已兑现(fulfilled)*:意味着操作成功完成。
- *已拒绝(rejected)*:意味着操作失败。

```js
/**
* 使用await 关键字,将返回Promise对象的异步方法变成同步方法
*/
async function queryList() {
loading.value = true
try {
const page = await listUser(queryParams.value)
loading.value = false
tableData.value = page.records;
queryParams.value.total = page.total;
} catch (e) {
}
}
```
使用 await async 将Promise的异步请求 转换成 同步的方法
# 编写用户的saveOrupdate接口
## 后端
```java
/**
* 用户的添加或者更细
* @param user
* @return
*/
@PostMapping("/saveOrUpdate")
public Ret saveOrUpdate(@RequestBody UmsUser user){
boolean success = userService.saveOrUpdate(user);
return Ret.ok(success);
}
```
> APIfox的邀请连接
狐友K4d9 在 Apifox 邀请你加入团队 哈学院java+移动开发1班 https://app.apifox.com/invite?token=ptbTNSXjmF75EDPzD2UfV
````json
{
"username":"zhangfei"
}
````

## 前端
edit.vue
```vue
保存
Cancel
```
> 父组件

效果

# 响应日期格式设置
> springboot中使用的json库 jackson
- 序列化: 从内存------其他的格式(String-json)
- 返序列化 : 从其他格式(JSON格式的String字符串) 转换成 内存对象
```yaml
spring:
# 配置数据源
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/yyzx?characterEncoding=utf-8
username: root
password: root
# 设置日期格式 以及时区
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
```

修改之后的

# 初始化修改页面
## 定义接口 ,根据id查询对象
```java
/**
* 根据主键查询
* @param id
* @return
*/
@GetMapping("/get/{id}")
public Ret get (@PathVariable int id){
UmsUser user = userService.getById(id);
return Ret.ok(user);
}
```
## 前端的API接口
src\api\permission\user\index.js
```js
/**
*
* @param {根据主键查询用户} id
* @returns
*/
export function getById(id) {
return request({
url: '/user/get/' + id,
method: 'get'
})
}
```



## src\views\permission\user\edit.vue
```vue
editId:{{ editId }}
保存
Cancel
```
# 用户删除
- 后端java接口
- 前端封装请求API
- 调用删除的API
## 后端接口
```jva
/**
* 根据主键删除
*
* /user/id
* method delete
* @param id
* @return
*/
@DeleteMapping("/{id}")
public Ret deleteById (@PathVariable int id){
return Ret.ok(userService.removeById(id));
}
```
## 前端封装API
src\api\permission\user\index.js
```js
/**
*
* @param {根据主键查询用户} id
* @returns
*/
export function removeById(id) {
return request({
url: '/user/' + id,
method: 'delete'
})
}
```
## 列表页中调用删除
导入删除API

定义删除按钮的事件方法
```js
//删除 按钮 点击事件
const pageRemoveById = (id) => {
ElMessageBox.confirm('确认删除?', 'Warning', { confirmButtonText: '确认', cancelButtonText: '取消', type: 'warning', })
.then(() => {
removeById(id).then(success => {
ElNotification({
title: '消息',
type: 'success',
message: "删除成功",
})
queryList()
})
})
}
```
删除按钮中调用事件方法

效果

# 练习CRUD
## 编写接口
- RoleController
- MenuController


## 前端页面
定义接口API
- src\api\permission\menu\index.js
- src\api\permission\role\index.js

### 角色页面
### 菜单的前端代码
删除show 字段, show 是 关键字
```
ALTER TABLE `yyzx`.`ums_menu`
DROP COLUMN `show`;
```
# 登录功能开发
## 制作登录界面
## JWT


### 生成和解析jwt
根据提供的载荷、签名生成 jwt格式的字符串
添加依赖
```xml
com.auth0
java-jwt
4.5.0
```
单元测试
```java
package com.neuedu.yyzx.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.Verification;
import org.junit.jupiter.api.Test;
/**
* @author 金山
* 项目:yyzx
* site: https://blog.fulfill.com.cn
* 描述
* @data 2025/3/2713:54
*/
public class JWTTest {
//秘钥
String secure = "adsafdsandfsafdsafdsaf";
//生成token
@Test
public void create(){
String username = "root";
String jwtToken = JWT.create().withClaim("username",username).
//加密
sign(Algorithm.HMAC256(secure));
System.out.println("jwtToken = " + jwtToken);
}
//解析token
@Test
public void verify(){
String jwtToken1 = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIn0.EYnHBAh2Ke56qmxDUHpn028rIwb4uW3P1_y_Xp5vRp0";
String jwtToken2 = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InJvb3QifQ.WtAf408msfqbJsKglKjMVHLNz3MSoYpkp4w_GFeAIuA";
JWTVerifier require = JWT.require(Algorithm.HMAC256(secure)).build();
//如果不报错,证明 token的格式 没问题
DecodedJWT decodedJWT = require.verify(jwtToken2);
//获取 用户 载荷的 数据
Claim usernameClaim = decodedJWT.getClaim("username");
String username = usernameClaim.asString();
System.out.println("username = " + username);
}
}
```
## 密码加密的问题
- 数据库中不能存明文
- 密码不可逆
### BCryptPasswordEncoder是一种基于BCrypt算法的单向加密工具,用于密码的安全存储和验证。
### 1. **基本原理**
BCryptPasswordEncoder采用[BCrypt算法](https://www.baidu.com/s?wd=BCrypt算法&rsv_idx=2&tn=25017023_18_dg&usm=2&ie=utf-8&rsv_pq=9e56d09400004c6f&oq=bcryptpasswordencoder原理&rsv_t=cef4f%2B7%2FT8y2vDBlwvqiMMH%2B3Zt6nw0h8Q2rmOl5SCsH4o4WS%2BCTMFTiTP7is3vKiRonpxc&rsv_dl=re_dqa_generate&sa=re_dqa_generate)
对密码进行加密,这是一种单向哈希算法,意味着加密后的密码无法被解密。其核心特点包括:
- **随机盐值**
:每次加密时都会生成一个随机的[盐值](https://www.baidu.com/s?wd=盐值&rsv_idx=2&tn=25017023_18_dg&usm=2&ie=utf-8&rsv_pq=9e56d09400004c6f&oq=bcryptpasswordencoder原理&rsv_t=5ee73HJKTr1ZujjDgcQ6jmhWs1ZnHTWNo2Ga30FLGLZkeehltK5QyDryBmI1bmGbtNmQmaU&rsv_dl=re_dqa_generate&sa=re_dqa_generate)
,确保相同的密码每次加密后得到不同的结果,增加安全性。
- **哈希运算**:将原始密码与盐值结合,通过BCrypt算法进行哈希运算,生成最终的密文。
### 2. **加密过程**
1. **生成盐值**:系统自动生成一个16字节的随机盐值。
2. **合并与哈希**:将原始密码与盐值合并,通过BCrypt算法进行哈希运算,生成密文。
3. **存储结果**:将盐值与密文拼接在一起,作为最终的加密结果存储在数据库中。
### 3. **密码验证**
在用户登录时,BCryptPasswordEncoder通过以下步骤验证密码:
1. **提取盐值**:从存储的加密结果中提取盐值。
2. **重新加密**:使用相同的盐值对用户输入的密码进行哈希运算,生成新的密文。
3. **比对结果**:将新生成的密文与数据库中存储的密文进行比对,如果一致则验证通过。
### 4. **安全性设计**
- **不可逆性**:由于使用了哈希算法,加密后的密码无法被解密,即使数据库泄露,攻击者也无法直接获取原始密码。
- **抗彩虹表攻击**:随机盐值的引入使得彩虹表攻击失效,因为相同的密码每次加密后都会生成不同的密文。
```xml
org.springframework.security
spring-security-crypto
6.3.7
```
使用 BCryptPasswordEncoder测试
```java
package com.neuedu.yyzx.utils;
import org.junit.jupiter.api.Test;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
* @author 金山
* 项目:yyzx
* site: https://blog.fulfill.com.cn
* 描述
* @data 2025/3/2714:30
*/
public class BCryptPasswordEncoderTest {
/**
* 加密
*/
@Test
public void encode(){
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String password = "123456";
String encode = passwordEncoder.encode(password);
System.out.println(encode);
//$2a$10$PzbuhV5w9tLIQWTazxCEv.AWfaWlpkwf/FgdAOfMkWoSq89/JMDFC
//$2a$10$2F/VggfJcOkmW6SkEZkS0ObxzuZWOslUbxwSI0uu9s1WJSb.eHJWa
//张三 123456 $2a$10$PzbuhV5w9tLIQWTazxCEv.AWfaWlpkwf/FgdAOfMkWoSq89/JMDFC
//张三 123456 $2a$10$2F/VggfJcOkmW6SkEZkS0ObxzuZWOslUbxwSI0uu9s1WJSb.eHJWa
}
/**
* 解密
*/
@Test
public void decode(){
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String rawPassword = "123456";
String password1 = "$2a$10$PzbuhV5w9tLIQWTazxCEv.AWfaWlpkwf/FgdAOfMkWoSq89/JMDFC";
String password2 = "$2a$10$2F/VggfJcOkmW6SkEZkS0ObxzuZWOslUbxwSI0uu9s1WJSb.eHJWa";
String password3 = "$2a$10$2F/VggfJcOkmW6SkEZkS121211210ObxzuZWOslUbxwSI0uu9s1WJSb.eHJWa";
boolean matches1 = passwordEncoder.matches(rawPassword, password1);
boolean matches2 = passwordEncoder.matches(rawPassword, password2);
boolean matches3 = passwordEncoder.matches(rawPassword, password3);
System.out.println("matches1 = " + matches1);
System.out.println("matches2 = " + matches2);
System.out.println("matches3 = " + matches3);
}
}
```
## 存储用户秘钥
添加、修改的时候需要将 铭文 加密存储
将BCryptPasswordEncoder放到IOC容器中通用
```
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
```
## 编写LoginController
用于验证 用户名密码 以及生成 token
```java
package com.neuedu.yyzx.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.neuedu.yyzx.common.Ret;
import com.neuedu.yyzx.po.UmsUser;
import com.neuedu.yyzx.service.UmsUserService;
import com.neuedu.yyzx.utils.JWTUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author 金山
* 项目:yyzx
* site: https://blog.fulfill.com.cn
* 描述
* @data 2025/3/288:50
*/
@RestController
public class LoginController {
@Autowired
private UmsUserService userService;
@Autowired
private PasswordEncoder passwordEncoder;
@RequestMapping("/login")
public Ret login(UmsUser user) throws Exception {
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(UmsUser::getUsername, user.getUsername());
UmsUser queryUser = userService.getOne(queryWrapper);
if(queryUser == null){
throw new Exception("用户名和密码不正确");
}
String encodePassword = queryUser.getPassword();
boolean matches = passwordEncoder.matches(user.getPassword(), encodePassword);
if(!matches){
throw new Exception("用户名和密码不正确");
}
//密码正确
//生成Token
String token = JWTUtils.create(queryUser);
return Ret.ok(token);
}
}
```
测试


## 使用 login.vue 发送用户名密码
- 编写API(function login(user:username、password))
- 表单绑定一下事件
### API
> src\api\permission\login\index.js
```
import request from '@/utils/request'
/***
* 查询列表
* query{username、password}
* */
export function login(query) {
return request({
url: '/login',
method: 'get',
params: query
})
}
```
登录表单 请求API
### Login.vue
```vue
```
## 使用Pinia框架存储token
已经安装过了
- 创建一个store(仓库)
- 在login.vue中存储 token 到store
### 定义store
```js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
//定义的仓库
state: () => ({
token: null, // 存储token
userInfo: null // 可以存储其他用户信息,如用户名、角色等
}),
actions: {
// 设置token
setToken(token) {
this.token = token;
},
// 移除token
removeToken() {
this.token = null;
// 这里也可以清除其他用户信息
this.userInfo = null;
},
},
getters: {
getToken() {
return this.token
},
}
});
```
### 在login.vue中设置token到pinia
```vue
```
### 对 token的操作进行封装
> src\utils\auth.js
```js
import { useUserStore } from '@/stores/userStore.js'
/**
*
* @param {设置token 到 pinia} token
*/
export function setToken(token) {
const userStore = useUserStore();
userStore.setToken(token)
}
export function getToken() {
const userStore = useUserStore();
return userStore.getToken
}
```
使用
### 在登录的时候调用 setoken

### 在发起网络请求的时候 携带token

## 路由守卫
当没有登录的时候需要 判断导航是否允许切换
> src\router\index.js
````js
const whitelist = ['/login']
// 全局路由守卫 当未登录并且访问的不是白名单的路径,需要切换到 login
router.beforeEach((to, from, next) => {
const path = to.path;
const token = getToken()
if (token || whitelist.indexOf(path) != -1) {
console.log("已经登录");
next()
} else {
console.log("未登录");
next("/login")
}
})
````
## 编写拦截器验证用户身份
拦截器
```java
package com.neuedu.yyzx.interceptor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.neuedu.yyzx.common.Ret;
import com.neuedu.yyzx.utils.JSONWriteUtils;
import com.neuedu.yyzx.utils.JWTUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import java.io.PrintWriter;
/**
* @author 金山
* 项目:yyzx
* site: https://blog.fulfill.com.cn
* 描述
* @data 2025/3/2811:32
*/
@Component
public class LoginInterceptor implements HandlerInterceptor {
//用于判断是否 允许放行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
/*
* 获取token
* 2.1 有token 放行
* 2.2 没有token 组织
*/
String token = request.getHeader("Authorization");
if(token == null || token.trim().length() ==0){ // 401
System.out.println("异常的情况 ");
JSONWriteUtils.write(response, Ret.unauthorized());
return false;
}
boolean tokenVerify = JWTUtils.verify(token);
tokenVerify = false;
if(!tokenVerify){
System.out.println("无效的Token "); // 403
JSONWriteUtils.write(response, Ret.forbidden());
return false;
}
System.out.println("正常的token");
return true;
}
}
```
配置拦截器
```java
package com.neuedu.yyzx.config;
import com.neuedu.yyzx.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
// @EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
/**
* 设置允许跨域
* @param registry
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/*/**")
.allowedOrigins("*")
.allowedMethods("*")
.maxAge(3600);
// Add more mappings...
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**") //大部分是需要 拦截
.excludePathPatterns("/login"); //例外的
}
}
```

# 需求
已发送需求规格说明书(原型)

# 数据库的设计
## 入住:
- 客户表: 客户的基本信息( 房间表的id 床位号) 姓名、 年龄 身份证号.....
- 床位
- 房间的
- 常数项(type、 item) (Y N 是 否 ) (F M 男 女)
- 数据:
- 申请外出的类型
- 申请退住的类型
- 血型
- 性别
## 设置护理级别:
- 客户表: 新增 字段 护理级别
- 护理级别表(CRUD 基础信息维护 一级 、二级、三级 )
- 护理项目表:(CRUD)
-

- 护理项目配置( 级别id 项目id )
- 中间表 : 客户、护理级别、护理项目
## 设置护理对象
管理员操作, 给 护工 添加(设置) 护理对象(客户)
- 用户表(护工表)
- 客户表: 护工id(用户id)
## 护理


- 护理记录: 客户的护理项目
-

- 申请外出
- 客户基本 申请时间 理由 【状态】
- 申请退住
- 客户基本 申请时间 理由 【状态】
## 设置客户喜好
- 客户表 添加如下字段
- 爱好
- 注意事项
- 备注

# Powerdesigner
- Sysbase
PowerDesigner165: 设置数据库的物理模型 逻辑模型 ER UML 各种
## 安装

\




设置开始菜单的信息

## 破解

创建一个文件

## 添加表

## 修改数据库库类型



## 编辑表的信息
双击之后可以编辑

## 设置数据库的字段类型


设置主键


## 定制了下 表单columns视图中的 属性列

## name2Comment.vbs
```vbscript
Option Explicit
ValidationMode = True
InteractiveMode = im_Batch
Dim mdl ' the current model
' get the current active model
Set mdl = ActiveModel
If (mdl Is Nothing) Then
MsgBox "There is no current Model "
ElseIf Not mdl.IsKindOf(PdPDM.cls_Model) Then
MsgBox "The current model is not an Physical Data model. "
Else
ProcessFolder mdl
End If
' This routine copy name into comment for each table, each column and each view
' of the current folder
Private sub ProcessFolder(folder)
Dim Tab 'running table
for each Tab in folder.tables
if Tab.comment ="" then
Tab.comment= Tab.name
end if
if not tab.isShortcut then
' Do not modify table comments
' tab.comment = tab.name
Dim col ' running column
for each col in tab.columns
if col.comment ="" then
col.comment= col.name
end if
next
end if
next
Dim view 'running view
for each view in folder.Views
if not view.isShortcut then
view.comment = view.name
end if
next
' go into the sub-packages
Dim f ' running folder
For Each f In folder.Packages
if not f.IsShortcut then
ProcessFolder f
end if
Next
end sub
```


## 生成物理模型的sql脚本



## 逆向工程
- 从SQL脚本(sql文件)---> PDM
- 从数据库----->PDM
- window
提前准备好数据库脚本
test.sql
```sql
/*==============================================================*/
/* Database name: yyzx */
/* DBMS name: MySQL 5.0 */
/* Created on: 2025/4/3 9:38:46 */
/*==============================================================*/
drop database if exists yyzx;
/*==============================================================*/
/* Database: yyzx */
/*==============================================================*/
create database yyzx;
use yyzx;
/*==============================================================*/
/* Table: backdown */
/*==============================================================*/
create table backdown
(
id int not null auto_increment comment '主键',
remarks varchar(100),
is_deleted int not null comment '逻辑删除标记(0:显示;1:隐藏)',
customer_id int not null comment '客户ID',
retreattime date not null comment '退住时间',
retreattype int not null comment '退住类型 0-正常退住 1-死亡退住 2-保留床位',
retreatreason varchar(100),
auditstatus int not null comment '审批状态 0-已提交 1-同意 2-拒绝',
auditperson varchar(100),
audittime date default NULL comment '审批时间',
primary key (id)
)
ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC;
/*==============================================================*/
/* Table: bed */
/*==============================================================*/
create table bed
(
id int not null auto_increment comment '主键',
room_no int not null comment '房间编号',
bed_status int not null comment '房间状态 1:空闲 2:有人 3:外出',
remarks varchar(255),
bed_no varchar(20),
primary key (id)
)
ENGINE=InnoDB AUTO_INCREMENT=53 DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC;
/*==============================================================*/
/* Table: beddetails */
/*==============================================================*/
create table beddetails
(
id int not null auto_increment comment '主键',
start_date date default NULL comment '床位起始日期',
end_date date default NULL comment '床位结束日期',
bed_details varchar(255),
customer_id int default NULL comment '顾客ID',
bed_id int default NULL comment '床位ID',
is_deleted int default NULL comment '逻辑删除标记(0:显示;1:隐藏)',
primary key (id)
)
ENGINE=InnoDB AUTO_INCREMENT=88 DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC;
/*==============================================================*/
/* Table: customer */
/*==============================================================*/
create table customer
(
id int not null auto_increment comment '主键',
is_deleted int not null comment '逻辑删除标记(0:显示;1:隐藏)',
customer_name varchar(20),
customer_age int not null comment '年龄',
customer_sex int not null comment '性别 0:男 1:女',
idcard varchar(20),
room_no varchar(20),
building_no varchar(11),
checkin_date date not null comment '入住时间',
expiration_date date not null comment '合同到期时间',
contact_tel varchar(20),
bed_id int not null comment '床号',
psychosomatic_state varchar(255),
attention varchar(255),
birthday date default NULL comment '出生日期',
height varchar(20),
weight varchar(20),
blood_type varchar(20),
filepath varchar(50),
user_id int default NULL comment '关联系统健康管家(护工) 无管家设置 -1',
level_id int default NULL comment '护理等级',
family_member varchar(20),
primary key (id),
key nurselevel_customer_fk (level_id),
key user_customer_fk (user_id)
)
ENGINE=InnoDB AUTO_INCREMENT=64 DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC;
/*==============================================================*/
/* Table: customernurseitem */
/*==============================================================*/
create table customernurseitem
(
id int not null auto_increment comment '主键',
item_id int not null comment '护理项目编号',
custormer_id int not null comment '客户编号',
level_id int default NULL comment '护理级别编号',
nurse_number int not null comment '购买数量',
is_deleted int default NULL comment '逻辑删除标记(0:显示;1:隐藏)',
buy_time date not null comment '服务购买日期',
maturity_time date not null comment '服务到期日',
primary key (id)
)
ENGINE=InnoDB AUTO_INCREMENT=156 DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC;
/*==============================================================*/
/* Table: customerpreference */
/*==============================================================*/
create table customerpreference
(
id int not null,
customer_id int default NULL,
preferences varchar(30) default NULL,
attention varchar(30) default NULL,
remark varchar(30) default NULL,
is_deleted int default NULL,
primary key (id)
)
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*==============================================================*/
/* Table: food */
/*==============================================================*/
create table food
(
id int not null,
food_name varchar(30) default NULL,
food_type varchar(30) default NULL,
price decimal(10,2) default NULL,
is_halal int default NULL,
food_img varchar(500) default NULL,
primary key (id)
)
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*==============================================================*/
/* Table: meal */
/*==============================================================*/
create table meal
(
id int not null,
week_day varchar(10) not null,
food_id int default NULL,
meal_type int default NULL,
taste varchar(30) default NULL,
is_deleted int default NULL,
primary key (id)
)
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*==============================================================*/
/* Table: menu */
/*==============================================================*/
create table menu
(
id int not null auto_increment comment '主键',
menus_index varchar(5),
title varchar(50),
icon varchar(50),
path varchar(100),
parent_id int default NULL comment '父级Id',
primary key (id)
)
ENGINE=InnoDB AUTO_INCREMENT=32 DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC;
/*==============================================================*/
/* Table: nursecontent */
/*==============================================================*/
create table nursecontent
(
id int not null auto_increment comment '主键',
serial_number varchar(20),
nursing_name varchar(55),
service_price varchar(55),
message varchar(100),
status int not null comment '状态 1:启用;2:停用',
execution_cycle varchar(20),
execution_times varchar(20),
is_deleted int default NULL comment '逻辑删除标记(0:显示;1:隐藏)',
primary key (id)
)
ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC;
/*==============================================================*/
/* Table: nurselevel */
/*==============================================================*/
create table nurselevel
(
id int not null auto_increment comment '主键',
level_name varchar(20),
level_status int default NULL comment '级别状态 1:启用;2:停用',
is_deleted int default NULL comment '逻辑删除标记(0:显示;1:隐藏)',
primary key (id)
)
ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC;
/*==============================================================*/
/* Table: nurselevelitem */
/*==============================================================*/
create table nurselevelitem
(
id int not null auto_increment comment '主键',
level_id int not null comment '关联护理级别',
item_id int not null comment '关联护理项目',
primary key (id),
key nurselevel_id_fk (level_id),
key nurseItem_id_fk (item_id)
)
ENGINE=InnoDB AUTO_INCREMENT=101 DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC;
/*==============================================================*/
/* Table: nurserecord */
/*==============================================================*/
create table nurserecord
(
id int not null auto_increment comment '主键',
is_deleted int not null comment '逻辑删除标记(0:显示;1:隐藏)',
customer_id int not null comment '客户ID',
item_id int not null comment '护理项目ID',
nursing_time datetime not null comment '护理时间',
nursing_content varchar(255),
nursing_count int not null comment '护理数量',
user_id int not null comment '护理人员ID',
primary key (id)
)
ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC;
/*==============================================================*/
/* Table: outward */
/*==============================================================*/
create table outward
(
id int not null auto_increment comment '主键',
remarks varchar(100),
is_deleted int not null comment '逻辑删除标记(0:显示;1:隐藏)',
customer_id int not null comment '客户ID',
outgoingreason varchar(100),
outgoingtime date not null comment '外出时间',
expectedreturntime date not null comment '预计回院时间',
actualreturntime date default NULL comment '实际回院时间',
escorted varchar(50),
relation varchar(50),
escortedtel varchar(50),
auditstatus int not null comment '审批状态 0-已提交 1-同意 2-拒绝',
auditperson varchar(50),
audittime date default NULL comment '审批时间',
primary key (id)
)
ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC;
/*==============================================================*/
/* Table: role */
/*==============================================================*/
create table role
(
id int not null auto_increment comment '主键',
create_time datetime,
update_time datetime,
update_by int default NULL comment '更新人',
is_deleted int not null comment '逻辑删除标记(0:显示;1:隐藏)',
name varchar(50),
primary key (id)
)
ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC;
/*==============================================================*/
/* Table: rolemenu */
/*==============================================================*/
create table rolemenu
(
id int not null auto_increment comment '主键',
role_id int not null comment '角色编号',
menu int not null comment '菜单编号',
primary key (id)
)
ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC;
/*==============================================================*/
/* Table: room */
/*==============================================================*/
create table room
(
id int not null auto_increment comment '主键',
room_floor varchar(10),
room_no int not null comment '房间编号',
primary key (id)
)
ENGINE=InnoDB AUTO_INCREMENT=38 DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC;
/*==============================================================*/
/* Table: user */
/*==============================================================*/
create table user
(
id int not null auto_increment comment '主键',
create_time date not null comment '创建时间',
create_by int not null comment '创建者',
update_time date default NULL comment '更新时间',
update_by int default NULL comment '更新者',
is_deleted int not null comment '逻辑删除标记(0:显示;1:隐藏)',
nickname varchar(20),
username varchar(20),
password varchar(20),
sex int not null comment '性别(0:女 1:男)',
email varchar(254),
phone_number varchar(20),
role_id int not null comment '系统角色编号(1-管理员 2-健康管家)',
primary key (id)
)
ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC;
```
- 创建物理模型


选择脚本


### 生成成功

# SWagger
**[Swagger](http://apifox.com/apiskills/what-is-swagger/)** 是一个开源的 API 设计和文档工具,它可以帮助开发人员更快、更简单地设计、构建、文档化和测试 [**RESTful
API**](http://apifox.com/apiskills/rest-api/)。Swagger 可以自动生成交互式 API 文档、客户端 SDK、服务器 stub 代码等,从而使开发人员更加容易地开发、测试和部署 API。
# Knife4j
Knife4j是一个集Swagger2 和 OpenAPI3 为一体的增强解决方案
Swagger2
OpenAPI3
编写一个接口 用于 计算两个数字的除法运算
method:get
传递两个参数 int numa int numb
{
code : 状态
data: 代表controller数据返回 除法的结果
msg: 错误消息
}
## 编写完controller
```
package com.neuedu.yyzx.controller;
import com.neuedu.yyzx.common.Ret;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author 金山
* 项目:yyzx
* site: https://blog.fulfill.com.cn
* 描述
* @data 2025/3/2514:04
*/
@RestController
public class IndexController {
@RequestMapping("/")
String index(){
return "success";
}
@RequestMapping("/div")
Ret div(int numa ,int numb){
return Ret.ok("结果: "+(numa / numb));
}
}
```
## 集成 knife4j
https://doc.xiaominfo.com/docs/quick-start
- 添加依赖
```xml
com.github.xiaoymin
knife4j-openapi3-jakarta-spring-boot-starter
4.4.0
```

## 修改application.yaml
```yaml
spring:
# 配置数据源
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/yyzx?characterEncoding=utf-8
username: root
password: root
# 设置日期格式 以及时区
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
# springdoc-openapi项目配置
springdoc:
swagger-ui:
path: /swagger-ui.html
tags-sorter: alpha
operations-sorter: alpha
api-docs:
path: /v3/api-docs
group-configs:
- group: 'default'
paths-to-match: '/**'
packages-to-scan: com.neuedu.yyzx.controller
# knife4j的增强配置,不需要增强可以不配
knife4j:
enable: true
setting:
language: zh_cn
```
## 修改controller

## 访问
最后,访问Knife4j的文档地址:`http://ip:port/doc.html`即可查看文档
http://localhost:8080/doc.html

## OpenAPI3 常用注解
参考; https://blog.csdn.net/shgg2917/article/details/143649659
### @Tag(name = "数学运算模块")
Controller 用于分组
### @Operation(summary = "除法运算")
给接口命名

### @Parameters 声明参数 ,

### @Schema
用于修饰 ,请求参数是对象类型, 返回结果是对象类型


# 使用重新建表的数据库
- 重新创建数据库
- 生成代码(java xml )
- 处理 原来使用的是ums_user xxxxx 替换成去掉 ums_
在IDEA中 使用 CTRL+SHIFT+R

将拦截器放开,并添加swagger 例外的API
````java
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**") //大部分是需要 拦截
.excludePathPatterns("/login",
"/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**","/v3/api-docs/*",
"/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs/*"
); //例外的
}
````
# 权限管理
使用5张表存储 权限 信息
- user
- role
- menu:
- role_menu: 角色有那些菜单
- user_role 用户 有那些角色
重新构造了menu表,存储
- 需要解决的问题
- 存储权限
- 后端需要校验 请求某一些controller( /test/list )方法的时候
- 需要识别用户(token)
- 获取用户权限
- 验证用户是否有访问方法的 权限
- { ... ... .. .. ... . ...... }
- 前端
- 登录之后 拉取用户权限列表
- 菜单动态查询
- 动态路由
- https://router.vuejs.org/zh/guide/advanced/dynamic-routing.html
- 按钮权限判断
# 管理5张表
- 用户、角色 CRUD OK
## 菜单表重新开发
添加字段
````sql
SET FOREIGN_KEY_CHECKS=0;
DROP TABLE IF EXISTS `role_menu`;
DROP TABLE IF EXISTS menu;
CREATE TABLE menu (
menu_id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
menu_name VARCHAR(50) NOT NULL COMMENT '菜单名称',
parent_id BIGINT(20) DEFAULT 0 COMMENT '父菜单ID',
order_num INT(4) DEFAULT 0 COMMENT '显示顺序',
path VARCHAR(200) DEFAULT '' COMMENT '路由地址',
component VARCHAR(255) DEFAULT NULL COMMENT '组件路径',
QUERY VARCHAR(255) DEFAULT NULL COMMENT '路由参数',
route_name VARCHAR(50) DEFAULT '' COMMENT '路由名称',
is_frame INT(1) DEFAULT 1 COMMENT '是否为外链(0是 1否)',
is_cache INT(1) DEFAULT 0 COMMENT '是否缓存(0缓存 1不缓存)',
menu_type CHAR(1) DEFAULT '' COMMENT '菜单类型(M目录 C菜单 F按钮)',
visible CHAR(1) DEFAULT 0 COMMENT '菜单状态(0显示 1隐藏)',
STATUS CHAR(1) DEFAULT 0 COMMENT '菜单状态(0正常 1停用)',
perms VARCHAR(100) DEFAULT NULL COMMENT '权限标识',
icon VARCHAR(100) DEFAULT '#' COMMENT '菜单图标',
create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
create_time DATETIME COMMENT '创建时间',
update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
update_time DATETIME COMMENT '更新时间',
remark VARCHAR(500) DEFAULT '' COMMENT '备注',
PRIMARY KEY (menu_id)
) ENGINE=INNODB AUTO_INCREMENT=1 COMMENT = '菜单权限表';
CREATE TABLE `role_menu` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
`menu_id` BIGINT DEFAULT NULL COMMENT 'id',
`role_id` BIGINT DEFAULT NULL COMMENT 'id',
`create_time` DATETIME DEFAULT NULL COMMENT '创建时间',
`create_by` VARCHAR(200) DEFAULT NULL COMMENT '创建人',
`update_by` VARCHAR(200) DEFAULT NULL COMMENT '更新人',
`update_time` DATETIME DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `FK_Reference_3` (`menu_id`),
KEY `FK_Reference_4` (`role_id`),
CONSTRAINT `FK_Reference_3` FOREIGN KEY (`menu_id`) REFERENCES `menu` (`menu_id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `FK_Reference_4` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='角色菜单表';
SET FOREIGN_KEY_CHECKS=1
````
### 树形表格
[Table 表格 | Element Plus (element-plus.org)](http://element-plus.org/zh-CN/component/table.html#树形数据与懒加载)

- 后端接口提供数据
- 
- 前端表格展示数据
- yyzx-vue/src/views/permission/menu/index.vue
- 修改页面添加一些字段
- yyzx-vue/src/views/permission/menu/edit.vue
## 菜单编辑(选择上级菜单)
使用ElementPlus提供的 TreeSelect
[TreeSelect 树形选择 | Element Plus (element-plus.org)](http://element-plus.org/zh-CN/component/tree-select.html#treeselect-树形选择)
## 使用测试数据构造菜单
```sql
-- 清除原始数据
DELETE FROM menu;
insert into `menu`(`menu_id`,`menu_name`,`parent_id`,`order_num`,`path`,`component`,`QUERY`,`route_name`,`is_frame`,`is_cache`,`menu_type`,`visible`,`STATUS`,`perms`,`icon`,`create_by`,`create_time`,`update_by`,`update_time`,`remark`) values (1,'系统管理',0,1,'system',NULL,'','',1,0,'M','0','0','','ZoomIn','admin','2025-04-15 09:17:15','',NULL,'系统管理目录'),(2,'系统监控',0,2,'monitor',NULL,'','',1,0,'M','0','0','','Crop','admin','2025-04-15 09:17:15','',NULL,'系统监控目录'),(3,'系统工具',0,3,'tool',NULL,'','',1,0,'M','0','0','','More','admin','2025-04-15 09:17:15','',NULL,'系统工具目录'),(100,'用户管理',1,1,'user','system/user/index','','',1,0,'C','0','0','system:user:list',NULL,'admin','2025-04-15 09:17:15','',NULL,'用户管理菜单'),(101,'角色管理',1,2,'role','system/role/index','','',1,0,'C','0','0','system:role:list',NULL,'admin','2025-04-15 09:17:15','',NULL,'角色管理菜单'),(102,'菜单管理',1,3,'menu','system/menu/index','','',1,0,'C','0','0','system:menu:list',NULL,'admin','2025-04-15 09:17:15','',NULL,'菜单管理菜单'),(103,'部门管理',1,4,'dept','system/dept/index','','',1,0,'C','0','0','system:dept:list',NULL,'admin','2025-04-15 09:17:15','',NULL,'部门管理菜单'),(104,'岗位管理',1,5,'post','system/post/index','','',1,0,'C','0','0','system:post:list',NULL,'admin','2025-04-15 09:17:15','',NULL,'岗位管理菜单'),(105,'字典管理',1,6,'dict','system/dict/index','','',1,0,'C','0','0','system:dict:list',NULL,'admin','2025-04-15 09:17:15','',NULL,'字典管理菜单'),(106,'参数设置',1,7,'config','system/config/index','','',1,0,'C','0','0','system:config:list',NULL,'admin','2025-04-15 09:17:15','',NULL,'参数设置菜单'),(107,'通知公告',1,8,'notice','system/notice/index','','',1,0,'C','0','0','system:notice:list',NULL,'admin','2025-04-15 09:17:15','',NULL,'通知公告菜单'),(108,'日志管理',1,9,'log','','','',1,0,'M','0','0','',NULL,'admin','2025-04-15 09:17:15','',NULL,'日志管理菜单'),(109,'在线用户',2,1,'online','monitor/online/index','','',1,0,'C','0','0','monitor:online:list',NULL,'admin','2025-04-15 09:17:15','',NULL,'在线用户菜单'),(110,'定时任务',2,2,'job','monitor/job/index','','',1,0,'C','0','0','monitor:job:list',NULL,'admin','2025-04-15 09:17:15','',NULL,'定时任务菜单'),(111,'数据监控',2,3,'druid','monitor/druid/index','','',1,0,'C','0','0','monitor:druid:list',NULL,'admin','2025-04-15 09:17:15','',NULL,'数据监控菜单'),(112,'服务监控',2,4,'server','monitor/server/index','','',1,0,'C','0','0','monitor:server:list',NULL,'admin','2025-04-15 09:17:16','',NULL,'服务监控菜单'),(113,'缓存监控',2,5,'cache','monitor/cache/index','','',1,0,'C','0','0','monitor:cache:list',NULL,'admin','2025-04-15 09:17:16','',NULL,'缓存监控菜单'),(114,'缓存列表',2,6,'cacheList','monitor/cache/list','','',1,0,'C','0','0','monitor:cache:list',NULL,'admin','2025-04-15 09:17:16','',NULL,'缓存列表菜单'),(115,'表单构建',3,1,'build','tool/build/index','','',1,0,'C','0','0','tool:build:list',NULL,'admin','2025-04-15 09:17:16','',NULL,'表单构建菜单'),(116,'代码生成',3,2,'gen','tool/gen/index','','',1,0,'C','0','0','tool:gen:list',NULL,'admin','2025-04-15 09:17:16','',NULL,'代码生成菜单'),(117,'系统接口',3,3,'swagger','tool/swagger/index','','',1,0,'C','0','0','tool:swagger:list',NULL,'admin','2025-04-15 09:17:16','',NULL,'系统接口菜单'),(500,'操作日志',108,1,'operlog','monitor/operlog/index','','',1,0,'C','0','0','monitor:operlog:list',NULL,'admin','2025-04-15 09:17:16','',NULL,'操作日志菜单'),(501,'登录日志',108,2,'logininfor','monitor/logininfor/index','','',1,0,'C','0','0','monitor:logininfor:list',NULL,'admin','2025-04-15 09:17:16','',NULL,'登录日志菜单'),(1000,'用户查询',100,1,'','','','',1,0,'F','0','0','system:user:query',NULL,'admin','2025-04-15 09:17:16','',NULL,''),(1001,'用户新增',100,2,'','','','',1,0,'F','0','0','system:user:add',NULL,'admin','2025-04-15 09:17:16','',NULL,''),(1002,'用户修改',100,3,'','','','',1,0,'F','0','0','system:user:edit',NULL,'admin','2025-04-15 09:17:16','',NULL,''),(1003,'用户删除',100,4,'','','','',1,0,'F','0','0','system:user:remove',NULL,'admin','2025-04-15 09:17:16','',NULL,''),(1004,'用户导出',100,5,'','','','',1,0,'F','0','0','system:user:export',NULL,'admin','2025-04-15 09:17:16','',NULL,''),(1005,'用户导入',100,6,'','','','',1,0,'F','0','0','system:user:import',NULL,'admin','2025-04-15 09:17:16','',NULL,''),(1006,'重置密码',100,7,'','','','',1,0,'F','0','0','system:user:resetPwd',NULL,'admin','2025-04-15 09:17:16','',NULL,''),(1007,'角色查询',101,1,'','','','',1,0,'F','0','0','system:role:query',NULL,'admin','2025-04-15 09:17:16','',NULL,''),(1008,'角色新增',101,2,'','','','',1,0,'F','0','0','system:role:add',NULL,'admin','2025-04-15 09:17:16','',NULL,''),(1009,'角色修改',101,3,'','','','',1,0,'F','0','0','system:role:edit',NULL,'admin','2025-04-15 09:17:16','',NULL,''),(1010,'角色删除',101,4,'','','','',1,0,'F','0','0','system:role:remove',NULL,'admin','2025-04-15 09:17:16','',NULL,''),(1011,'角色导出',101,5,'','','','',1,0,'F','0','0','system:role:export',NULL,'admin','2025-04-15 09:17:16','',NULL,''),(1012,'菜单查询',102,1,'','','','',1,0,'F','0','0','system:menu:query',NULL,'admin','2025-04-15 09:17:16','',NULL,''),(1013,'菜单新增',102,2,'','','','',1,0,'F','0','0','system:menu:add',NULL,'admin','2025-04-15 09:17:16','',NULL,''),(1014,'菜单修改',102,3,'','','','',1,0,'F','0','0','system:menu:edit',NULL,'admin','2025-04-15 09:17:17','',NULL,''),(1015,'菜单删除',102,4,'','','','',1,0,'F','0','0','system:menu:remove',NULL,'admin','2025-04-15 09:17:17','',NULL,''),(1016,'部门查询',103,1,'','','','',1,0,'F','0','0','system:dept:query',NULL,'admin','2025-04-15 09:17:17','',NULL,''),(1017,'部门新增',103,2,'','','','',1,0,'F','0','0','system:dept:add',NULL,'admin','2025-04-15 09:17:17','',NULL,''),(1018,'部门修改',103,3,'','','','',1,0,'F','0','0','system:dept:edit',NULL,'admin','2025-04-15 09:17:17','',NULL,''),(1019,'部门删除',103,4,'','','','',1,0,'F','0','0','system:dept:remove',NULL,'admin','2025-04-15 09:17:17','',NULL,''),(1020,'岗位查询',104,1,'','','','',1,0,'F','0','0','system:post:query',NULL,'admin','2025-04-15 09:17:17','',NULL,''),(1021,'岗位新增',104,2,'','','','',1,0,'F','0','0','system:post:add',NULL,'admin','2025-04-15 09:17:17','',NULL,''),(1022,'岗位修改',104,3,'','','','',1,0,'F','0','0','system:post:edit',NULL,'admin','2025-04-15 09:17:17','',NULL,''),(1023,'岗位删除',104,4,'','','','',1,0,'F','0','0','system:post:remove',NULL,'admin','2025-04-15 09:17:17','',NULL,''),(1024,'岗位导出',104,5,'','','','',1,0,'F','0','0','system:post:export',NULL,'admin','2025-04-15 09:17:17','',NULL,''),(1025,'字典查询',105,1,'#','','','',1,0,'F','0','0','system:dict:query',NULL,'admin','2025-04-15 09:17:17','',NULL,''),(1026,'字典新增',105,2,'#','','','',1,0,'F','0','0','system:dict:add',NULL,'admin','2025-04-15 09:17:17','',NULL,''),(1027,'字典修改',105,3,'#','','','',1,0,'F','0','0','system:dict:edit',NULL,'admin','2025-04-15 09:17:17','',NULL,''),(1028,'字典删除',105,4,'#','','','',1,0,'F','0','0','system:dict:remove',NULL,'admin','2025-04-15 09:17:17','',NULL,''),(1029,'字典导出',105,5,'#','','','',1,0,'F','0','0','system:dict:export',NULL,'admin','2025-04-15 09:17:17','',NULL,''),(1030,'参数查询',106,1,'#','','','',1,0,'F','0','0','system:config:query',NULL,'admin','2025-04-15 09:17:17','',NULL,''),(1031,'参数新增',106,2,'#','','','',1,0,'F','0','0','system:config:add',NULL,'admin','2025-04-15 09:17:17','',NULL,''),(1032,'参数修改',106,3,'#','','','',1,0,'F','0','0','system:config:edit',NULL,'admin','2025-04-15 09:17:17','',NULL,''),(1033,'参数删除',106,4,'#','','','',1,0,'F','0','0','system:config:remove',NULL,'admin','2025-04-15 09:17:17','',NULL,''),(1034,'参数导出',106,5,'#','','','',1,0,'F','0','0','system:config:export',NULL,'admin','2025-04-15 09:17:17','',NULL,''),(1035,'公告查询',107,1,'#','','','',1,0,'F','0','0','system:notice:query',NULL,'admin','2025-04-15 09:17:17','',NULL,''),(1036,'公告新增',107,2,'#','','','',1,0,'F','0','0','system:notice:add',NULL,'admin','2025-04-15 09:17:17','',NULL,''),(1037,'公告修改',107,3,'#','','','',1,0,'F','0','0','system:notice:edit',NULL,'admin','2025-04-15 09:17:17','',NULL,''),(1038,'公告删除',107,4,'#','','','',1,0,'F','0','0','system:notice:remove',NULL,'admin','2025-04-15 09:17:17','',NULL,''),(1039,'操作查询',500,1,'#','','','',1,0,'F','0','0','monitor:operlog:query',NULL,'admin','2025-04-15 09:17:17','',NULL,''),(1040,'操作删除',500,2,'#','','','',1,0,'F','0','0','monitor:operlog:remove',NULL,'admin','2025-04-15 09:17:17','',NULL,''),(1041,'日志导出',500,3,'#','','','',1,0,'F','0','0','monitor:operlog:export',NULL,'admin','2025-04-15 09:17:17','',NULL,''),(1042,'登录查询',501,1,'#','','','',1,0,'F','0','0','monitor:logininfor:query',NULL,'admin','2025-04-15 09:17:17','',NULL,''),(1043,'登录删除',501,2,'#','','','',1,0,'F','0','0','monitor:logininfor:remove',NULL,'admin','2025-04-15 09:17:17','',NULL,''),(1044,'日志导出',501,3,'#','','','',1,0,'F','0','0','monitor:logininfor:export',NULL,'admin','2025-04-15 09:17:17','',NULL,''),(1045,'账户解锁',501,4,'#','','','',1,0,'F','0','0','monitor:logininfor:unlock',NULL,'admin','2025-04-15 09:17:17','',NULL,''),(1046,'在线查询',109,1,'#','','','',1,0,'F','0','0','monitor:online:query',NULL,'admin','2025-04-15 09:17:18','',NULL,''),(1047,'批量强退',109,2,'#','','','',1,0,'F','0','0','monitor:online:batchLogout',NULL,'admin','2025-04-15 09:17:18','',NULL,''),(1048,'单条强退',109,3,'#','','','',1,0,'F','0','0','monitor:online:forceLogout',NULL,'admin','2025-04-15 09:17:18','',NULL,''),(1049,'任务查询',110,1,'#','','','',1,0,'F','0','0','monitor:job:query',NULL,'admin','2025-04-15 09:17:18','',NULL,''),(1050,'任务新增',110,2,'#','','','',1,0,'F','0','0','monitor:job:add',NULL,'admin','2025-04-15 09:17:18','',NULL,''),(1051,'任务修改',110,3,'#','','','',1,0,'F','0','0','monitor:job:edit',NULL,'admin','2025-04-15 09:17:18','',NULL,''),(1052,'任务删除',110,4,'#','','','',1,0,'F','0','0','monitor:job:remove',NULL,'admin','2025-04-15 09:17:18','',NULL,''),(1053,'状态修改',110,5,'#','','','',1,0,'F','0','0','monitor:job:changeStatus',NULL,'admin','2025-04-15 09:17:18','',NULL,''),(1054,'任务导出',110,6,'#','','','',1,0,'F','0','0','monitor:job:export',NULL,'admin','2025-04-15 09:17:18','',NULL,''),(1055,'生成查询',116,1,'#','','','',1,0,'F','0','0','tool:gen:query',NULL,'admin','2025-04-15 09:17:18','',NULL,''),(1056,'生成修改',116,2,'#','','','',1,0,'F','0','0','tool:gen:edit',NULL,'admin','2025-04-15 09:17:18','',NULL,''),(1057,'生成删除',116,3,'#','','','',1,0,'F','0','0','tool:gen:remove',NULL,'admin','2025-04-15 09:17:18','',NULL,''),(1058,'导入代码',116,4,'#','','','',1,0,'F','0','0','tool:gen:import',NULL,'admin','2025-04-15 09:17:18','',NULL,''),(1059,'预览代码',116,5,'#','','','',1,0,'F','0','0','tool:gen:preview',NULL,'admin','2025-04-15 09:17:18','',NULL,''),(1060,'生成代码',116,6,'#','','','',1,0,'F','0','0','tool:gen:code',NULL,'admin','2025-04-15 09:17:18','',NULL,'');
```
## 用户授权角色
给每个用户添加按钮,展示所有的角色列表用于勾选
、



修改表字段
```sql
ALTER TABLE `yyzx_second`.`user_role`
CHANGE `user_ud` `user_id` BIGINT NULL COMMENT '用户id';
```
## 角色授权菜单
角色的修改,页面添加勾选菜单的功能
- 展示菜单树
- 保存的时候 携带这所有勾选的菜单id
### 展示菜单树
`el_tree`组件 在 edit.vue中添加组件
```\
```
- data 表述树形菜单的数据(查询所有菜单项 父子关系维护)
- 初始化的时候查询
- 1 角色基础数据 role
- 2 所有菜单列表 以父子形式维护 allMenuList
- 
- show-checkbox 显示checbox复选框
- node-key : menuId 用于 初始化页面之后 勾选已经授权的菜单
- 初始化页面的方法

### 后端RoleController
编写一个初始化页面的方法


### 保存角色(授权菜单)
```
//将已授权的菜单id放到form中
const chekedKeys = treeRef.value.getCheckedKeys()
form.value.grantMenuIds = chekedKeys;
```



# 加载用户信息
## 后端

### 改造了下 JWTUtils
```java
package com.neuedu.yyzx.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.neuedu.yyzx.po.User;
/**
* @author 金山
* 项目:yyzx
* site: https://blog.fulfill.com.cn
* 描述
* @data 2025/3/288:55
*/
public class JWTUtils {
// jwt 秘钥
private static String secret = "adsafsafdsafdsafdsadsadsafdsafsafsafsafafa";
public static String create(User user) {
String jwtToken = JWT.create()
.withClaim("id", user.getId())
.withClaim("username", user.getUsername())
.withClaim("nickname", user.getNickname())
.withClaim("lastlogin", user.getLastlogin())
// 加密
.sign(Algorithm.HMAC256(secret));
return jwtToken;
}
/**
* 验证token是否有效
*
* @param token
* @return
*/
public static boolean verify(String token) {
boolean success = false;
try {
JWTVerifier require = JWT.require(Algorithm.HMAC256(secret)).build();
// 如果不报错,证明 token的格式 没问题
DecodedJWT decodedJWT = require.verify(token);
success = true;
} catch (Exception e) {
}
return success;
}
/**
* 获取Token中UserId
* @param token
* @return
*/
public static Long getUserId(String token) {
Long userId = null;
try {
JWTVerifier require = JWT.require(Algorithm.HMAC256(secret)).build();
// 如果不报错,证明 token的格式 没问题
DecodedJWT decodedJWT = require.verify(token);
userId = decodedJWT.getClaim("id").asLong();
} catch (Exception e) {
}
return userId;
}
}
```
### UserService

## 前端
pinia useUserStore


### 改造router
在切换路由的时候进行判断, 如果存在 token , 判断如果没有身份信息,则加载

### 动态加载菜单
yyzx-vue/src/components/NavMenu.vue
```vue
```
# 退出清空权限信息
添加一个清空缓存的方法


在auth.js

NavMenu.vue
替换logout方法 变为调用pinia store 的action(方法 ): 清空token userInfo、用户权限,,,,,

# 动态路由
思路就是在 加载用户身份的时候,动态添加路由
## 默认的静态路由表

[通过调用 router.adRoute方法动态添加路由](https://router.vuejs.org/zh/guide/advanced/dynamic-routing.html#%E5%9C%A8%E5%AF%BC%E8%88%AA%E5%AE%88%E5%8D%AB%E4%B8%AD%E6%B7%BB%E5%8A%A0%E8%B7%AF%E7%94%B1)
```
router.addRoute(buildDynamic(permissions));
```

## 构建动态路由的函数
```js
/**
* 通过菜单权限构造动态路由
* @param permissions
* @returns {{path: string, component: {}, children: *[], name: string}}
*/
function buildDynamic(permissions) {
let dynamicRoutes = [];
//一级菜单是文件夹
permissions.forEach(menu => {
if (menu.children) {
//二级菜单 路由 menu.children
menu.children.forEach(({path, component, menuName}) => {
dynamicRoutes.push({
path: '/' + path,
name: path,
// component: () => import('../views/permission/role/index.vue'),
component: ()=>{
const exists = existsView('../views/'+component+'.vue')
if(exists){
return import('../views/'+component+'.vue')
}else{
console.log('../views/'+component+'.vue ::::: 不存在 ' )
return import('../views/404.vue')
}
},
})
})
}
})
console.log("==动态路由, children ; dynamicRoutes",dynamicRoutes)
//构造好的
const allRoutes = {
path: '/',
name: 'home',
component: HomeView,
children: dynamicRoutes
}
// console.log("组装好的 路由表: ",allRoutes)
return allRoutes;
}
```
判断模块是否存在,使用的是vite import.meta.glob函数 [](https://vitejs.cn/vite5-cn/guide/features.html#glob-import)
## 编写404

# 后端验证用户权限
## 请求用户是谁(token )
```java
public static Long getUserId(String token) {
Long userId = null;
try {
JWTVerifier require = JWT.require(Algorithm.HMAC256(secret)).build();
// 如果不报错,证明 token的格式 没问题
DecodedJWT decodedJWT = require.verify(token);
userId = decodedJWT.getClaim("id").asLong();
} catch (Exception e) {
}
return userId;
}
```
## 获取用户权限列表
(user--- user-role --role_menu--> menu )
```xml
```
## 自定义注解 PreAuthorize
```java
package com.neuedu.yyzx.config.security;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author 金山
* 项目:yyzx
* site: https://blog.fulfill.com.cn
* 描述
* @data 2025/4/178:34
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface PreAuthorize {
/**
* 校验的权限名称
* @return
*/
String value() default "";
}
```
在Controller方法中声明需要哪些权限

## 编写一个环绕通知

```java
package com.neuedu.yyzx.config.security;
import com.neuedu.yyzx.common.Ret;
import com.neuedu.yyzx.po.Menu;
import com.neuedu.yyzx.service.UserService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* @author 金山
* 项目:yyzx
* site: https://blog.fulfill.com.cn
* 描述 权限增强
* @data 2025/4/1614:34
*/
@Aspect
@Component
@EnableAspectJAutoProxy
public class SecurityAdvice {
@Autowired
UserService userService;
@Autowired
SecurityUtils securityUtils;
@Pointcut("execution(* com.neuedu.yyzx.controller.*Controller.*(..))")
public void controllerMethods(){}
@Around("controllerMethods()")
public Object around(ProceedingJoinPoint point){
Object result = null;
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
System.out.println("method = " + method);
PreAuthorize annotation = null;
String authorize = null;
//获取权限注解
if(method != null){
annotation = method.getAnnotation(PreAuthorize.class);
}
// 获取权限注解中的 校验值
if(annotation != null){
authorize = annotation.value();
}
//判断校验权限是否通过
if(authorize != null && !"".equals(authorize.trim())){
System.out.println("authorize = " + authorize);
boolean success = validatePermission(authorize);
System.out.println("validatePermission: success = " + success);
//没有需要验证的权限
if(!success){
return Ret.forbidden();
}
}
//执行controller方法
try {
result = point.proceed(point.getArgs());
} catch (Throwable e) {
throw new RuntimeException(e);
}
return result;
}
/**
* 验证用户是否拥有权限
* @param authorize
* @return
*/
private boolean validatePermission(String authorize) {
List