# ccc-System **Repository Path**: ccc3gc/ccc-system ## Basic Information - **Project Name**: ccc-System - **Description**: 简单的基于SpringBoot的用户管理系统(半成品) - **Primary Language**: Unknown - **License**: AFL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-01-31 - **Last Updated**: 2024-02-27 ## Categories & Tags **Categories**: Uncategorized **Tags**: JavaScript, Java, H5, CSS, Vue ## README # 药企内部管理平台 # 1.环境搭建 > 需要安装以下软件: ```java 如果开发一些软件,java,需要哪些必须的软件? 0. JDK11 1. IDEA--->IDE 2. 数据库管理系统软件 MySQL8.0+ 3. Navicat 4. Axure Vscode 5. typora----> md文件 6. Git---> Gitee 码云 Github ``` # 2. 技术选型 ```java 前后端分离方式 前端: html css js(重点) es6语法 Vue(指令)+组件(暂且不用)+Axios 交互 后端: SpringBoot2(Spring+SpringMVC) +Mybatis3.*+其它 注解开发 ``` # 3.项目搭建 ## 3.1 前端项目 ```java 使用VScode创建前端项目: 1. 创建前端页面 ``` ## 3.2 后端项目 ```java 创建springboot: 1. 使用idea创建maven工程 2. 使用项目引导器 maven: 项目构建工具 1.管理依赖(jar) 1.1 本机有本地仓库 1.2 从配置的云仓库下载 2.创建聚合工程 ``` ### 3.2.1 pom.xml ```xml 4.0.0 com.by.medical zhenggong-medical-system 1.0-SNAPSHOT 11 11 UTF-8 spring-boot-starter-parent org.springframework.boot 2.6.4 org.springframework.boot spring-boot-starter-web ``` ### 3.2.2 application.yml ```yaml server: port: 9000 ``` ### 3.2.3 启动类 ```java @SpringBootApplication public class MedicalApplication {//启动类----> 运行run public static void main(String[] args) { SpringApplication.run(MedicalApplication.class,args); } } ``` ### 3.2.4 测试 > 在浏览器/postman发起请求测试 http://127.0.0.1:9000/hello ```java @RestController public class HelloController { @GetMapping("/hello") public Map hello(){ return Map.of("name","炭治郎"); } } ``` ### 3.2.5 持久层 > 集成数据库 > 1.1 在pom.xml文件中 引入mybatis相关的依赖 ```java 1.mybatis 2.mysql的驱动 3.数据库连接池(druid) 4.lombok ``` ```xml 4.0.0 com.by.medical zhenggong-medical-system 1.0-SNAPSHOT 11 11 UTF-8 spring-boot-starter-parent org.springframework.boot 2.6.4 org.springframework.boot spring-boot-starter-web org.mybatis.spring.boot mybatis-spring-boot-starter 3.0.2 mysql mysql-connector-java com.alibaba druid-spring-boot-starter 1.2.16 org.projectlombok lombok ``` > 1.2 在application.yml文件中,配置连接数据库相关的信息 ```yaml server: port: 9000 #配置数据库相关 spring: datasource: username: root password: root url: jdbc:mysql://127.0.0.1:3306/medical driver-class-name: com.mysql.cj.jdbc.Driver druid: initial-size: 10 max-active: 20 mybatis: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl type-aliases-package: com.by.medical.mapper ``` > 1.3 修改项目核心配置 加载mapper ```java @SpringBootApplication @MapperScan("com.by.medical.mapper") public class MedicalApplication {//启动类----> 运行run public static void main(String[] args) { SpringApplication.run(MedicalApplication.class,args); } } ``` ### 3.2.6 SysUser ```java @Data public class SysUser implements java.io.Serializable{ private Integer id; private String userTrueName; private String password; private String jobNumber; private Integer gender; private String phone; private String email; private String wechat; private String qqNumber; private String userImage; private String address; private Integer deptId; private Integer jobId; private Integer regionId; private Integer provinceId; private Integer groupId; private Integer productGroupId; private java.time.LocalDateTime createTime; private java.time.LocalDateTime updateTime; } ``` ### 3.2.7 创建mapper接口 > 在项目中 新建mapper包 ```java @Mapper public interface SysUserMapper { List queryAll(); } ``` ### 3.2.8 创建mapper映射 > 在项目的resources目录下,创建mapper目录,将mapper映射文件存储在mapper目录下 ```xml ``` ### 3.2.9 修改application.yml ```yaml server: port: 9000 #配置数据库相关 spring: datasource: username: root password: root url: jdbc:mysql://127.0.0.1:3306/medical driver-class-name: com.mysql.cj.jdbc.Driver druid: initial-size: 10 max-active: 20 mybatis: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl type-aliases-package: com.by.medical.bean mapper-locations: mapper/*.xml ``` ## 3.3 建库建表 > 1. 创建数据库 > 2. 创建表 ```sql CREATE TABLE `sys_user` ( `id` int NOT NULL AUTO_INCREMENT COMMENT '用户id,自增', `user_true_name` varchar(255) DEFAULT NULL COMMENT '用户真实姓名', `password` varchar(255) DEFAULT NULL COMMENT '用户密码 初始化密码123456', `job_number` varchar(255) DEFAULT NULL COMMENT '用户工号', `gender` tinyint(1) DEFAULT '0' COMMENT '用户性别 0 男 1 女 默认男', `phone` varchar(255) DEFAULT NULL COMMENT '用户手机号码', `email` varchar(255) DEFAULT NULL COMMENT '邮箱', `wechat` varchar(255) DEFAULT NULL COMMENT '微信号', `qq_number` varchar(255) DEFAULT NULL COMMENT 'qq号', `user_image` varchar(255) DEFAULT NULL COMMENT '用户头像', `address` varchar(255) DEFAULT NULL COMMENT '联系地址', `dept_id` int DEFAULT NULL COMMENT '部门id', `job_id` int DEFAULT NULL COMMENT '职位id', `region_id` int DEFAULT NULL COMMENT '大区id', `province_id` int DEFAULT NULL COMMENT '地区id', `group_id` int DEFAULT NULL COMMENT '组id', `product_group_id` int DEFAULT NULL COMMENT '产品组id', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '新增时间', `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; ``` # 4. 实现需求 ## 4.1 登录 ```java 分析: 根据手机号+密码,工号+密码 ``` ### 4.1.1 页面 ```html
``` ```javascript ``` ### 4.1.2 controller ```java @GetMapping("/login") public AxiosResult userLogin(@RequestParam("phoneOrNo") String phoneOrNo, @RequestParam("pass") String pass){ //String phoneOrNo,String pass 是前端提交数据 SysUser sysUser = sysUserMapper.userLogin(phoneOrNo,pass); if(sysUser==null){ return AxiosResult.error(StatusEnum.USER_LOGIN_ERROR); } return AxiosResult.success(sysUser); } ``` ### 4.1.3 mapper ```java @Mapper public interface SysUserMapper { List queryAll(); SysUser userLogin(@Param("phoneOrNo") String phoneOrNo, @Param("pass") String pass); } ``` ### 4.1.4 映射 > 修改SysUserMapper.xml内容 ```xml ``` ### 4.1.5 页面跳转 > 用户登录成功之后,应该要跳转到首页 ```java 1.创建index.html ok 2.在首页上,展示个人部分信息(用户真实名称,用户头像) 3.核心: 在index.html中获得并展示用户信息? 3.1 获得 登录成功之后 服务器响应完整的用户对象信息 3.2 困难点 把login.html内容传到index.html ``` #### 1.URLSearchParams ```java 解决方案: 1. 在跳转的时候 拼接想要的数据 在index.html 使用URLSearchParams ``` ```javascript let { userTrueName, userImage } = data; //页面跳转 location.replace("/medical-page/page/index.html?name=" + encodeURIComponent(userTrueName) + "&image=" + encodeURIComponent(userImage)); ``` ```jav 2. 在index.html获得页面跳转并携带的数据 2.1 在index.html 引入vue.js axios.js 2.2 利用vue的生命周期中钩子函数 获得数据 ``` ```javascript ``` #### ==2. 缓存== ```java //浏览器缓存: //localStorage----> 永远都不会丢失 除非自动删除 //sessionStorage----> session会话 session过期 关闭浏览器 ``` > 在login.html里面 ```javascript //2.第二种方式 将数据存储缓存 localStorage let {userTrueName, userImage,id } = data; //将数据转换成json数据字符串存储 let userObj = {name:userTrueName,image:userImage,id:id}; localStorage.setItem("loginUserInfo",JSON.stringify(userObj)); location.replace("/medical-page/page/index.html"); ``` > 在index.html中 ```javascript ``` ```javascript //2.第二种方式 将数据存储缓存 localStorage let {userTrueName, userImage,id } = data; //将数据转换成json数据字符串存储 let userObj = {name:userTrueName,image:userImage,id:id}; localStorage.setItem("loginUserInfo",JSON.stringify(userObj)); location.replace("/medical-page/page/index.html"); ``` ## 4.2 查询所有用户 > 1. 创建用户列表页面 medical-page/page/user/list.html > 2. 从缓存localStorage获得存储的用户信息(真实名称,用户头像) > 3. 查询用户信息 > 4. 查询单表(sys_user)是不对的,数据不完整。 ### 4.2.1 表设计 ```java //发现在信息展示里面,用户信息中,要展示用户所在的部门名称,用户职位 ``` ```sql CREATE TABLE `dept` ( `id` int NOT NULL AUTO_INCREMENT, `dept_name` varchar(255) DEFAULT NULL, `dept_loc` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; ``` ```sql CREATE TABLE `job_info` ( `id` int NOT NULL AUTO_INCREMENT, `job_name` varchar(255) DEFAULT NULL COMMENT '职务名称 角色', `job_desc` varchar(255) DEFAULT NULL COMMENT '职务描述', `job_status` tinyint(1) DEFAULT '1' COMMENT '0 false 1 true', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; ``` ### 4.2.2 编写sql ```sql -- 查询用户信息,并展示用户所在的部门名称,与职位名称 SELECT u.*,d.dept_name,j.job_name FROM sys_user AS u,dept AS d,job_info AS j WHERE u.dept_id=d.id AND u.job_id=j.id; ``` ### 4.2.3 VO ```java //mapper返回是xxxDTO //service里面将DTO转换成VO @Data public class SysUserVO implements Serializable { private Integer id; private String userTrueName; private String password; private String jobNumber; private Integer gender; private String phone; private String email; private String wechat; private String qqNumber; private String userImage; private String address; private Integer deptId; private Integer jobId; private Integer regionId; private Integer provinceId; private Integer groupId; private Integer productGroupId; private java.time.LocalDateTime createTime; private java.time.LocalDateTime updateTime; private String deptName; private String jobName; } ``` ### 4.2.4 controller ```java //查询用户信息,查询部门名称+职位名称 @GetMapping("/query") public AxiosResult> queryUserAndDeptAndJob(){ return AxiosResult.success(sysUserMapper.queryUserAndDeptAndJob()); } ``` ### 4.2.5 mapper ```java @Mapper public interface SysUserMapper { List queryAll(); SysUser userLogin(@Param("phoneOrNo") String phoneOrNo, @Param("pass") String pass); List queryUserAndDeptAndJob(); } ``` ```xml ``` ### 4.2.6 js ```js ``` ### 4.2.7 展示数据 ```html {{user.jobNumber}} {{user.userTrueName}} {{user.phone}} {{user.deptName}} {{user.jobName}} {{user.createTime}} ``` ## 4.3 新增用户 ```java 1. 修改list.html页面 创建新增用户模态框 2. 用户录入数据-----> 获得用户动态录入的所有的数据----->vue v-model insertUserObj 3. 发起异步请求-----> 持久化保存数据库-----> 编写insert 存储到sys_user 4. 操作dept,job_info 完成查询所有部门+职位 ``` ### 4.3.1 查询所有部门信息 ```sql SELECT * FROM dept ``` > 实体类 ```java @Data public class Dept implements Serializable { private Integer id; private String deptName; private String deptLoc; } ``` > controller ```java @RestController @RequestMapping("/api/dept/") public class DeptController { @Autowired private DeptMapper deptMapper; //查询所有部门 @GetMapping("/query") public AxiosResult> queryAllDept(){ return AxiosResult.success(deptMapper.queryAllDept()); } } ``` > mapper接口 ```java @Mapper public interface DeptMapper { List queryAllDept(); } ``` > mapper映射文件 ```xml ``` ### 4.3.2 查询所有的职位 ```java select * from job_info; ``` ```java @Data public class JobInfo implements Serializable { private Integer id; private String jobName; private String jobDesc; private Boolean jobStatus; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime createTime; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime updateTime; } ``` ```java @RestController @RequestMapping("/api/job/") public class JobInfoController { @Autowired private JobInfoMapper jobInfoMapper; @GetMapping("/query") public AxiosResult> queryAllJobInfo(){ return AxiosResult.success(jobInfoMapper.queryAllJobInfo()); } } ``` ```java @Mapper public interface JobInfoMapper { List queryAllJobInfo(); } ``` ```xml ``` ### 4.3.3 页面展示 > 在页面上展示所有部门,所有职位信息。 > 1.新增点击事件 > 2.编写js函数 ```html
``` ```html ``` ### 4.3.4 新增成功 ```html ``` ```js insertUser(){ //获得用户录入的信息 //异步调用 axios.post(`http://127.0.0.1:9000/api/user/insert`,this.insertUserObj) .then(response=>{ location.replace("/medical-page/page/user/list.html"); }); } }, ``` ```java //新增用户 @PostMapping("/insert") public AxiosResult insertUser(@RequestBody SysUser sysUser){ //System.out.println("获得新增用户信息:"+sysUser); sysUserMapper.insertUser(sysUser); return AxiosResult.success(); } @Mapper public interface SysUserMapper { void insertUser(SysUser sysUser); } ``` ```xml INSERT INTO sys_user (user_true_name, `password`, job_number, gender, phone, email, wechat, qq_number, address, dept_id, job_id) VALUES (#{userTrueName},#{password},#{jobNumber},#{gender},#{phone},#{email},#{wechat},#{qqNumber},#{address},#{deptId},#{jobId}) ``` ## 4.4 查看详情 > 查询单个用户信息。 ````java 1.在list.html页面中,找到"用户详情"按钮,新增事件。click 2.在vue实例中,创建新的函数queryUserById(uid) 3.编写sql ```` ```sql SELECT u.*,d.dept_name,j.job_name FROM sys_user AS u,dept AS d,job_info AS j WHERE u.id = #{id} AND u.dept_id=d.id AND u.job_id=j.id; ``` > controller ```java //查询单个用户 //http://127.0.0.1:9000/api/user/query/13 @GetMapping("/query/{uid}") public AxiosResult queryUserById(@PathVariable int uid){ return AxiosResult.success(sysUserMapper.queryById(uid)); } ``` > mapper接口 ```java SysUserVO queryById(int uid); ``` > mapper映射 ```xml ``` > html ```html ``` ```java MySQL数据库中,有哪些约束自带索引? 1.1 主键约束 1.2 外键约束 1.3 唯一性约束 index: 本质是就是数据结构。建议创建index。 提高查询的性能。Btree ``` ## 4.5 修改用户 ```java 分析: 修改个人信息。 查看个人原来信息的前提下,进行修改。设置新的数据。 update 在开发中,执行修改,删除,前提都要查询。 ``` ```java 1.修改与新增共用1个模态框 2.在修改按钮上,新增一个click事件,调用编写过的queryUserById(id) 3.会影响新增功能,在新增按钮上新增一个点击事件,addBtn 清除insertUserObj对象里面的数据 4.需要在页面: 5.在后端代码里面 根据用户是否提交id 判断 执行新增? 修改? ``` ```sql update sys_user set user_true_name=#{userTrueName}, `password`=#{password}, job_number=#{jobNumber}, gender=#{gender}, phone=#{phone}, email=#{email}, wechat=#{wechat}, qq_number=#{qqNumber}, address=#{address}, dept_id=#{deptId}, job_id=#{jobId} where id = #{id} ``` ```java //新增/修改用户 @PostMapping("/insertOrUpdate") public AxiosResult insertUser(@RequestBody SysUser sysUser){ Integer id = sysUser.getId(); if(id==null){ sysUserMapper.insertUser(sysUser); }else{ //执行修改 sysUserMapper.updateUser(sysUser); } return AxiosResult.success(); } ``` ```java void updateUser(SysUser sysUser); ``` ## 4.6 删除用户 > 根据id删除。(id 主键列 自带索引 根据id操作数据 比根据普通列操作 性能高) ```java 下午作业: 实现删除用户功能。 实现方式: 方式1.可以纯前端实现(js实现)----->不要编写后端代码 1.1 编写table标签,编写js实现删除一行内容 方式2.可以纯后端代码实现(编写后端接口) 2.1 编写后端代码,删除数据库1行记录 方式3. 前后端开发(既写前端页面,又写后端代码) ``` ### 4.6.1 删除单个 ```js deleteUserById(uid) { if (window.confirm("确认要删除吗?" + uid)) { //发起异步请求 调用后端接口 删除数据库数据 axios.delete(`http://127.0.0.1:9000/api/user/delete/` + uid) .then(response => { //页面继续跳转list.html location.replace = "/medical-page/page/user/list.html"; }); } }, ``` ```java //根据id删除用户 @DeleteMapping("/delete/{uid}") public AxiosResult deleteById(@PathVariable int uid){ sysUserMapper.deleteUserById(uid); return AxiosResult.success(); } ``` ```java void deleteUserById(int uid); ``` ```xml DELETE FROM sys_user WHERE id=#{id} ``` > 问题: 不能删除当前登录者 ```javascript deleteUserById(uid) { //判断删除的用户是否是登录者 //是 不能删除 //如何判断呢? 登录成功之后 localstorage //从缓存里面获得用户id let loginUserInfoStr = localStorage.getItem("loginUserInfo"); let loginUserObj = JSON.parse(loginUserInfoStr); let loginUserId = loginUserObj.id; if(uid==loginUserId){ alert("无法删除用户"); return; } if (window.confirm("确认要删除吗?" + uid)) { //发起异步请求 调用后端接口 删除数据库数据 axios.delete(`http://127.0.0.1:9000/api/user/delete/` + uid) .then(response => { //页面继续跳转list.html location.replace("/medical-page/page/user/list.html"); }); } }, ``` ### 4.6.2 批量删除 ```javascript deleteUserBatch(){ //1.获得用户选择要删除的多行记录id---->获得多个id //===> 获得多个复选框的value属性的数据 let idArary = []; let checkboxArray = document.querySelectorAll(".myCheckBox"); checkboxArray.forEach(checkBox=>{ if(checkBox.checked){ idArary.push(checkBox.value); } }); let flag = false; idArary.forEach(id=>{ if(id==this.loginUserId){ flag = true; } }); if(flag){ alert("无法删除登录者的信息"); return; } //可以删除 idArary.forEach(uid=>{ axios.delete(`http://127.0.0.1:9000/api/user/delete/` + uid); }); location.replace("/medical-page/page/user/list.html"); }, ``` ## 4.7 分页 > 分页查询用户信息 > 每页展示5条记录。 ```mysql SELECT u.*, d.dept_name, j.job_name FROM sys_user AS u, dept AS d, job_info AS j WHERE u.dept_id = d.id AND u.job_id = j.id ORDER BY u.id DESC LIMIT (page-1)*size,size ``` ### 4.7.1 前端页面 ```java 1.在页面上手动引入zpageNav.js 2.在页面引入分页标签 ``` ```javascript ``` ### 4.7.2 后端代码 > 引入分页依赖。 pageHelper.jar ```xml com.github.pagehelper pagehelper-spring-boot-starter 1.4.6 ``` ```java @Data public class PageResult { private int page; private int maxPage; //总页数 private int total;//总记录数 private List list; } ``` ```java //查询用户信息,查询部门名称+职位名称 @GetMapping("/query/{page}/{size}") public AxiosResult> queryUserAndDeptAndJob(@PathVariable("page") int page,@PathVariable("size") int size) { PageHelper.startPage(page, size); List userVOList = sysUserMapper.queryUserAndDeptAndJob(); PageInfo pageInfo = new PageInfo<>(userVOList); PageResult pageResult = new PageResult<>(); pageResult.setList(userVOList); pageResult.setPage(page); pageResult.setMaxPage(pageInfo.getPages()); pageResult.setTotal(pageInfo.getTotal()); return AxiosResult.success(pageResult); } ``` ## 4.8 条件+分页 ### 4.8.1 前端页面 ```java 1.在搜索框中,成功展示部门以及职位信息 。 2.找到搜索按钮,创建点击事件。 //分页查询用户信息 3.在queryAllUserInfo函数中: 满足实现的功能: 条件+分页 queryAllUserInfo() { //查询数据库的所有的用户信息 展示list.html //异步查询 //分页+条件 //1.参数: 分页数据 条件的数据 let params = { params:{ page: this.page, pageSize: this.pageSize, deptId: this.searchParams.deptId, jobId: this.searchParams.jobId, nameParam: this.searchParams.nameParam, phoneOrNumber: this.searchParams.phoneOrNumber, } }; axios.get(`http://127.0.0.1:9000/api/user/query`,params) .then(response => { // let { data } = response.data; // let { page, maxPage, total, list } = data; // this.page = page; // this.maxPage = maxPage; // this.total = total; // this.userList = list; }); }, ``` > 调试之后,前端js: ```javascript queryAllUserInfo() { //查询数据库的所有的用户信息 展示list.html //异步查询 //分页+条件 //1.参数: 分页数据 条件的数据 let params = { params:{ page: this.page, pageSize: this.pageSize, deptId: this.searchParams.deptId, jobId: this.searchParams.jobId, nameParam: this.searchParams.nameParam, phoneOrNumber: this.searchParams.phoneOrNumber, } }; axios.get(`http://127.0.0.1:9000/api/user/query`, params) .then(response => { let { data } = response.data; let { page, maxPage, total, list } = data; this.page = page; this.maxPage = maxPage; this.total = total; this.userList = list; //搜索相关的信息清除 this.searchParams.deptId=-1; this.searchParams.jobId=-1; this.searchParams.nameParam=""; this.searchParams.phoneOrNumber=""; }); }, ``` ```html 没有找到合适的数据 ``` ### 4.8.2 后端代码 ```java 在controller: //查询用户信息,查询部门名称+职位名称 @GetMapping("/query") public AxiosResult> queryUserAndDeptAndJob(SearchParamVO searchParamVO) { int page = searchParamVO.getPage(); int size = searchParamVO.getPageSize(); PageHelper.startPage(page, size); //传递条件数据,满足sql编写 List userVOList = sysUserMapper.queryUserAndDeptAndJob(searchParamVO); PageInfo pageInfo = new PageInfo<>(userVOList); PageResult pageResult = new PageResult<>(); pageResult.setList(userVOList); pageResult.setPage(page); pageResult.setMaxPage(pageInfo.getPages()); pageResult.setTotal(pageInfo.getTotal()); return AxiosResult.success(pageResult); } ``` ```java //在mapper接口 List queryUserAndDeptAndJob(SearchParamVO searchParamVO); ``` ```xml //在mapper的映射文件中 ``` > 问题: 在用户表中,存在很多外键列。外键列的数据不与我们主表里面的主键列的数据一致的话,我们进行普通关联查询,无法查询到的。为了显示所有用户信息,建议使用外连接。 ```xml ``` ## 4.9 用户头像上传 ### 4.9.1 前端页面 > 不单独编写上传页面,在新增/修改的模态框的form表单中,新增上传文件元素即可。 ```html ``` ```html
upload
``` ```javascript uploadImage() { let element = document.getElementById("uploadFile"); let uploadFile = element.files[0]; //调用后台接口 执行异步上传 //1.post //2.multipart/form-data 请求头 //3.FormData let param = new FormData(); param.append('uploadFile', uploadFile);//二进制文件 let config = { headers: { 'Content-type': 'multipart/form-data' }, }; axios.post(`http://127.0.0.1:9000/api/user/upload`, param, config).then(response => { let { status, data } = response.data; console.log(data); }); }, // uploadImage(e){ // //上传头像 // //1.获得用户选择的头像内容(文件内容) // //2.将二进制文件内容 传递到后端接口 // //3.js请求接口 // // 3.1 post // // 3.2 multipart/form-data // // 3.3 文件数据封装FormData // let uploadFile = e.target.files[0]; // let formData = new FormData(); // formData.append("uploadFile",uploadFile); // let config={ // headers:{ // 'content-type':'multipart/form-data', // } // }; // //异步上传 // axios.post(`http://127.0.0.1:9000/api/user/upload`,formData,config); // }, ``` ### 4.9.2 后端代码 ```java //上传用户头像 @PostMapping("/upload") public AxiosResult uploadUserImage(MultipartFile uploadFile, HttpServletRequest request) throws IOException { String originalFilename = uploadFile.getOriginalFilename();//获得上传文件名称以及后缀 //正常开发中: //1.存储外部服务器 (nginx) //2.云端服务器(OSS) 对象存储服务器 //后端维护用户上传的图片(本地磁盘) //把用户每天上传的图片 存储到以日期为准一个目录中 //弊端: 前端页面不能正常访问存储本地磁盘文件 //解决: 需要添加映射处理 // String realPath = request.getServletContext().getRealPath("/"); // System.out.println(realPath); String curDateStr = LocalDate.now().toString(); File targetDir = new File(UPLOAD_USER_DIR, curDateStr); if (!targetDir.exists()) { targetDir.mkdirs(); } String extension = StringUtils.getFilenameExtension(originalFilename); String name = UUID.randomUUID().toString().replaceAll("-", ""); String targetFileName = name + "." + extension; uploadFile.transferTo(new File(targetDir, targetFileName)); return AxiosResult.success("/upload/user/"+curDateStr+"/"+targetFileName); } ``` ```java @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceLocations("file:F:\\beiyou\\zhenggong_IT\\workspace\\zhenggong-medical-system\\src\\main\\resources\\static\\upload\\user"); } } ``` ## 4.10 数据导出/导入 > 数据导出: 将数据库/缓存数据导出至excel文件。 > 数据导入: 将excel文件里面的sheet中的信息导入数据库,永久存储。 ### 4.10.1 导出 ```java 需求: 将数据库里面所有用户信息导出到指定的exel文件中。 常用的技术: easyexcel 入门容易 ``` > 前端页面 ```html
``` ```javascript exportUserInfo() { //异步请求 myAxios.post(`http://127.0.0.1:9000/api/user/export`).then(response => { //创建下载提示 将成功返回的内容下载到文件中 //后端响应的类型 应该是字节 blob let data = response.data; const blob = new Blob([data], { type: "application/vnd.ms-excel;charset=utf-8", }); //blob就是获得到字节内容 let downloadLink = document.createElement("a"); downloadLink.href = window.URL.createObjectURL(blob); downloadLink.download = "导出用户信息.xlsx"; downloadLink.click();//自动下载到 "导出用户信息.xlsx" //问题: 下载文件成功 可能会出现文件打不开 //原因: axios发起异步请求 默认返回的json的数据 也就是说获得的data是json的数据 }); }, ``` ```javascript let myAxios = axios.create({ responseType:"blob", }); ``` > 后端 ```xml cn.afterturn easypoi-spring-boot-starter 4.4.0 ``` ```java //导出---->将用户信息导出到指定的文件中 @PostMapping("/export") public void exportUserInfo(HttpServletResponse response) throws IOException { //获得导出的所有用户信息 List sysUserList = sysUserMapper.queryAll(); ExportParams params = new ExportParams(); params.setTitle("用户列表信息"); params.setSheetName("用户sheet"); Workbook workbook = ExcelExportUtil.exportExcel(params, SysUser.class, sysUserList); //配置响应 response.setCharacterEncoding("UTF-8"); response.setHeader("content-type","application/vnd.ms-excel"); //服务器响应的不再是json数据,而是流文件(excel)文件 String fileName = URLEncoder.encode("userInfo", StandardCharsets.UTF_8); response.setHeader("Content-Disposition","attachment;filename="+fileName+".xlsx"); workbook.write(response.getOutputStream()); workbook.close(); } ``` ```java @Data public class SysUser implements java.io.Serializable{ private Integer id; //name的数据是对应的excel文件里面的列名 //orderNum是用来定义excel的第n列 从0开始 //width用来定义列的宽度 @Excel(name = "用户名",orderNum = "1",width = 20) private String userTrueName; private String password; @Excel(name = "工号",orderNum = "0") private String jobNumber; @Excel(name = "性别",replace = {"男_0","女_1"},orderNum = "2") private Integer gender; @Excel(name = "手机号",orderNum = "3",width = 20) private String phone; @Excel(name = "邮箱账号",orderNum = "4",width = 20) private String email; @Excel(name = "微信",orderNum = "5",width = 20) private String wechat; private String qqNumber; private String userImage; @Excel(name = "家庭地址",orderNum = "6",width = 20) private String address; private Integer deptId; private Integer jobId; private Integer regionId; private Integer provinceId; private Integer groupId; private Integer productGroupId; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @Excel(name = "入职时间",orderNum = "7",exportFormat = "yyyy-MM-dd HH:mm:ss",width = 30) private java.time.LocalDateTime createTime; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private java.time.LocalDateTime updateTime; } ``` ### 4.10.2 导入 ```java 导入: 将指定的excel文件里面用户信息,保存到数据库中。 excel文件内容是有模板要求(数据必须按照模板要求)-----> 将excel文件进行上传----> 调用sql 将数据存储 ``` > 前端(页面,js) ```html
导入
``` ```javascript importUserInfo(event) { //异步调用接口 实现导入 //1.获得用户选择的文件 //2.将文件内容传到服务器 //3.调用接口 //等价于====> 文件上传 // FormData post multipart/form-data let importFile = event.target.files[0]; let formData = new FormData(); formData.append("importFile", importFile); let config = { headers: { "content-type": "multipart/form-data", } }; axios.post(`http://127.0.0.1:9000/api/user/import`, formData, config).then(response => { location.replace("/medical-page/page/user/list.html"); }); }, ``` > 后端 ```java //导入----->将excel文件数据存储到数据库 @PostMapping("/import") public AxiosResult importUserInfo(MultipartFile importFile) throws Exception { //将页面提价的文件数据转成多个用户对象 // System.out.println(importFile.getOriginalFilename()); // System.out.println(importFile.getBytes().length); // System.out.println(importFile.getSize()); //将文件里面的每一行内容转换SysUser对象 //调用mapper接口 实现批量插入用户信息 //前提: // 1.引入easypoi的依赖 2.SysUser的相关的属性使用@Excel修饰 // 2.excel文件里面的列名不能随便写 必须与SysUser类中@Excel的name的值一致 //importExcel(InputStream inputstream, Class pojoClass,ImportParams params) //inputstream 文件内容 ImportParams importParams = new ImportParams(); importParams.setStartSheetIndex(0);//指定读取第index+1个sheet importParams.setTitleRows(1);//标题 importParams.setHeadRows(1);//头部 列 List sysUserList = ExcelImportUtil.importExcel(importFile.getInputStream(), SysUser.class, importParams); //将流的内容转换成指定的类型对象 //sysUserList.forEach(System.out::println); //批量新增 sysUserList.forEach(sysUser -> sysUser.setPassword("123456")); //sysUserList.forEach(System.out::println); sysUserMapper.insertUserBatch(sysUserList); return AxiosResult.success(); } ``` ```java void insertUserBatch(List sysUserList); ``` ```xml INSERT INTO sys_user (user_true_name, `password`, job_number, gender, phone, email, wechat, qq_number, address, dept_id, job_id) values (#{user.userTrueName}, #{user.password}, #{user.jobNumber}, #{user.gender}, #{user.phone}, #{user.email}, #{user.wechat}, #{user.qqNumber}, #{user.address}, #{user.deptId}, #{user.jobId}) ``` ## 4.11 密码加密 ```java MD5: MessageDigest 信息摘要算法 不可逆的。 Base64 ``` > 修改密码 只能修改个人密码。 > 前端页面 ```html
``` ```javascript updatePass(){ let params = { id:this.loginUserId, newPass:this.newPass, }; console.log(params); axios.post(`http://127.0.0.1:9000/api/user/pass`,params).then(response=>{ //跳轉到login.html //清除緩存裡面個人信息 //localStorage.clear(); localStorage.removeItem("loginUserInfo"); location.replace("/medical-page/login.html"); }); }, ``` > 后端 ```java public class MD5Util { private MD5Util(){} private static final String SALT = "%$**&7lISA^^^^524"; /** * 密码加密 * @param sourceStr 源密码 * @return 加密之后的数据 */ public static String encodeStr(@NonNull String sourceStr){ try { sourceStr+=SALT; MessageDigest messageDigest = MessageDigest.getInstance("MD5"); messageDigest.update(sourceStr.getBytes()); byte[] bytes = messageDigest.digest();//加密的数据 //换算 //将字节转换成16进制里面字符 //一个字节转换成2个16进制的字符 BigInteger bigInteger = new BigInteger(1,bytes); return bigInteger.toString(16); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } } ``` ```java //修改密码 @PostMapping("/pass") public AxiosResult updatePass(@RequestBody Map map){ Object id = map.get("id"); Object newPass = map.get("newPass");//用户设置的新密码 是明文数据 //对密码加密 String encodeStr = MD5Util.encodeStr(newPass.toString()); sysUserMapper.updatePass((int)id,encodeStr); return AxiosResult.success(); } ``` > 注意: 登录,新增,导入的功能中,也要对密码加密。 > 代码略 ## 4.12 登录拦截(略) ```xml com.auth0 java-jwt 4.3.0 ``` ```java public class TokenService { private static final String SECRET = "^%$LISA(($#$"; //生成token public String createToken(long adminId) { Algorithm algorithm = Algorithm.HMAC256(SECRET); return JWT.create() .withIssuer("lisa") .withSubject("登录成功的token") .withClaim("adminId",adminId) //.withExpiresAt(new Date(System.currentTimeMillis() + Duration.ofMinutes(2).toMillis())) .sign(algorithm); } //验证token public DecodedJWT verifyToken(String token) { Algorithm algorithm = Algorithm.HMAC256(SECRET); JWTVerifier verifier = JWT.require(algorithm) .withIssuer("lisa") .build(); return verifier.verify(token); } } ``` ```java @Component //注入容器 public class LoginInterceptor implements HandlerInterceptor { @Autowired private TokenService tokenService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //token验证 //在项目中 获得请求头里面的token String method = request.getMethod(); if ("OPTIONS".equals(method)) { return true; } String authorization = request.getHeader("Authorization"); if (!StringUtils.hasLength(authorization)) {//是空 //建议抛出异常 进行全局异常处理 throw new RuntimeException("用户未登录"); //return false; } String[] array = authorization.split(" "); if (array.length == 0) { throw new RuntimeException("用户未登录"); } if (!array[0].startsWith("Basic")) { throw new RuntimeException("用户未登录"); } try { tokenService.verifyToken(array[1]); } catch (Exception e) { throw new RuntimeException("用户未登录", e); } return true; } } ``` ```java @Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private LoginInterceptor loginInterceptor; @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceLocations("file:F:\\beiyou\\zhenggong_IT\\workspace\\zhenggong-medical-system\\src\\main\\resources\\static\\upload\\user"); } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor) .addPathPatterns("/api/**") .excludePathPatterns("/api/user/login") .order(0); } } ``` # 5. 封装接口数据 ```java //{ status:"200",msg:"查询成功",data:userObj} //{ status:"500",msg:"查询失败"} ``` ## 5.1 AxiosResult ```java @Getter @Setter //@JsonSerialize public class AxiosResult implements Serializable { //就是封装服务器响应给前端的数据 //T就是一个参数化数据类型 泛型 可以是任意一个引用数据类型 private String status; private String msg; private T data; private AxiosResult(StatusEnum statusEnum){ this.status = statusEnum.getStatus(); this.msg = statusEnum.getMsg(); } private AxiosResult(StatusEnum statusEnum,T data){ this.status = statusEnum.getStatus(); this.msg = statusEnum.getMsg(); this.data = data; } //静态方法 success error public static AxiosResult success() { return new AxiosResult<>(StatusEnum.SUCCESS); } public static AxiosResult success(T data) { return new AxiosResult<>(StatusEnum.SUCCESS,data); } public static AxiosResult success(StatusEnum statusEnum,T data) { return new AxiosResult<>(statusEnum,data); } public static AxiosResult error() { return new AxiosResult<>(StatusEnum.ERROR); } public static AxiosResult error(StatusEnum statusEnum) { return new AxiosResult<>(statusEnum); } } ``` ## 5.2 StatusEnum ```java @Getter @AllArgsConstructor public enum StatusEnum { //维护当前系统里面所有的错误码列表 SUCCESS("200","成功"), ERROR("500","失败"), USER_LOGIN_ERROR("501","用户名或者密码有误"); private final String status; private final String msg; } ``` # 6.跨域 ```java url: 协议 ip 端口 list.html:1 Access to XMLHttpRequest at 'http://127.0.0.1:9000/api/dept/query' from origin 'http://127.0.0.1:5500' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. ``` ```java @Configuration public class CorsConfig { //Filter------>CorsFilter @Bean public FilterRegistrationBean corsFilter() { FilterRegistrationBean bean = new FilterRegistrationBean<>(); CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.addAllowedOrigin("http://127.0.0.1:5500"); //corsConfiguration.setAllowedOrigins(); corsConfiguration.setAllowedMethods(List.of("POST", "GET", "PUT", "DELETE", "OPTION")); corsConfiguration.addAllowedHeader("*"); corsConfiguration.setAllowCredentials(false); UrlBasedCorsConfigurationSource configurationSource = new UrlBasedCorsConfigurationSource(); configurationSource.registerCorsConfiguration("/**", corsConfiguration); CorsFilter corsFilter = new CorsFilter(configurationSource); bean.setFilter(corsFilter); bean.setOrder(0); return bean; } } ```