直接用pure-design-vip生成模板 准备工作
springboot依赖
java版本的指定
maven的指定(配置好阿里云仓库)
npm的配置(前提安装好依赖)
springboot跨域配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlbasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfig {
// 当前跨域请求最大有效时长。这里默认1天
private static final long MAX_AGE = 24 * 60 * 60;
@Bean
public CorsFilter corsFilter() {
UrlbasedCorsConfigurationSource source = new UrlbasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("http://localhost:8080"); // 1 设置访问源地址
corsConfiguration.addAllowedHeader("*"); // 2 设置访问源请求头
corsConfiguration.addAllowedMethod("*"); // 3 设置访问源请求方法
corsConfiguration.setMaxAge(MAX_AGE);
source.registerCorsConfiguration("
@Bean
public Docket restApi() {
return new Docket(documentationType.SWAGGER_2)
.groupName("标准接口")
.apiInfo(apiInfo("Spring Boot中使用Swagger2构建RESTful APIs", "1.0"))
.useDefaultResponseMessages(true)
.forCodeGeneration(false)
.select()
.apis(RequestHandlerSelectors.basePackage("com.qingge.springboot.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo(String title, String version) {
return new ApiInfoBuilder()
.title(title)
.description("更多请关注: https://blog.csdn.net/xqnode")
.termsOfServiceUrl("https://blog.csdn.net/xqnode")
.contact(new Contact("xqnode", "https://blog.csdn.net/xqnode", "xiaqingweb@163.com"))
.version(version)
.build();
}
}
pom.xml
前后端数据交互 vue安装axiosio.springfox springfox-boot-starter 3.0.0
npm i axios -Srequest.js封装
import axios from 'axios'
const request = axios.create({
baseURL: '/api',
timeout: 5000
})
// request 拦截器
// 可以自请求发送前对请求做一些处理
// 比如统一加token,对请求参数统一加密
request.interceptors.request.use(config => {
config.headers['Content-Type'] = 'application/json;charset=utf-8';
// config.headers['token'] = user.token; // 设置请求头
return config
}, error => {
return Promise.reject(error)
});
// response 拦截器
// 可以在接口响应后统一处理结果
request.interceptors.response.use(
response => {
let res = response.data;
// 如果是返回的文件
if (response.config.responseType === 'blob') {
return res
}
// 兼容服务端返回的字符串数据
if (typeof res === 'string') {
res = res ? JSON.parse(res) : res
}
return res;
},
error => {
console.log('err' + error) // for debug
return Promise.reject(error)
}
)
export default request
数据请求测试
代码生成
mp依赖
com.baomidou mybatis-plus-generator3.5.1 org.apache.velocity velocity1.7
代码生成 CodeGenerator
package com.qingge.springboot.utils;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import java.util.Collections;
public class CodeGenerator {
public static void main(String[] args) {
generate();
}
private static void generate() {
FastAutoGenerator.create("jdbc:mysql://localhost:3306/qing?serverTimezone=GMT%2b8", "root", "123456")
.globalConfig(builder -> {
builder.author("青哥哥") // 设置作者
.enableSwagger() // 开启 swagger 模式
.fileOverride() // 覆盖已生成文件
.outputDir("D:\代码\小白做毕设2022\springboot\src\main\java\"); // 指定输出目录
})
.packageConfig(builder -> {
builder.parent("com.qingge.springboot") // 设置父包名
.moduleName(null) // 设置父包模块名
.pathInfo(Collections.singletonMap(OutputFile.mapperXml, "D:\代码\小白做毕设2022\springboot\src\main\resources\mapper\")); // 设置mapperXml生成路径
})
.strategyConfig(builder -> {
builder.entityBuilder().enableLombok();
// builder.mapperBuilder().enableMapperAnnotation().build();
builder.controllerBuilder().enableHyphenStyle() // 开启驼峰转连字符
.enableRestStyle(); // 开启生成@RestController 控制器
builder.addInclude("sys_user") // 设置需要生成的表名
.addTablePrefix("t_", "sys_"); // 设置过滤表前缀
})
// .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
.execute();
}
}
模板引擎,自定义配置(controller.java.vm)
package ${package.Controller};
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import ${package.Service}.${table.serviceName};
import ${package.Entity}.${entity};
#if(${restControllerStyle})
import org.springframework.web.bind.annotation.RestController;
#else
import org.springframework.stereotype.Controller;
#end
#if(${superControllerClassPackage})
import ${superControllerClassPackage};
#end
#if(${restControllerStyle})
@RestController
#else
@Controller
#end
@RequestMapping("#if(${package.ModuleName})/${package.ModuleName}#end/#if(${controllerMappingHyphenStyle})${controllerMappingHyphen}#else${table.entityPath}#end")
#if(${kotlin})
class ${table.controllerName}#if(${superControllerClass}) : ${superControllerClass}()#end
#else
#if(${superControllerClass})
public class ${table.controllerName} extends ${superControllerClass} {
#else
public class ${table.controllerName} {
#end
@Resource
private ${table.serviceName} ${table.entityPath}Service;
// 新增或者更新
@PostMapping
public boolean save(@RequestBody ${entity} ${table.entityPath}) {
return ${table.entityPath}Service.saveOrUpdate(${table.entityPath});
}
@DeleteMapping("/{id}")
public Boolean delete(@PathVariable Integer id) {
return ${table.entityPath}Service.removeById(id);
}
@PostMapping("/del/batch")
public boolean deleteBatch(@RequestBody List ids) {
return ${table.entityPath}Service.removeByIds(ids);
}
@GetMapping
public List<${entity}> findAll() {
return ${table.entityPath}Service.list();
}
@GetMapping("/{id}")
public ${entity} findOne(@PathVariable Integer id) {
return ${table.entityPath}Service.getById(id);
}
@GetMapping("/page")
public Page<${entity}> findPage(@RequestParam Integer pageNum,
@RequestParam Integer pageSize) {
QueryWrapper queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("id");
return ${table.entityPath}Service.page(new Page<>(pageNum, pageSize), queryWrapper);
}
}
#end
前端讲解
前端不是后端程序员重点,可copy我们的练手项目使用
导入导出接口实现
利用hutool工具实现(功能强大)https://www.hutool.cn/docs/#/poi/Excel%E5%B7%A5%E5%85%B7-ExcelUtil
pom依赖
cn.hutool hutool-all5.7.20 org.apache.poi poi-ooxml4.1.2
导出接口 起别名可在实体类用@Alias实现
@GetMapping("/export")
public void export(HttpServletResponse response) throws Exception {
// 从数据库查询出所有的数据
List list = userService.list();
// 通过工具类创建writer 写出到磁盘路径
// ExcelWriter writer = ExcelUtil.getWriter(filesUploadPath + "/用户信息.xlsx");
// 在内存操作,写出到浏览器
ExcelWriter writer = ExcelUtil.getWriter(true);
//自定义标题别名
writer.addHeaderAlias("username", "用户名");
writer.addHeaderAlias("password", "密码");
writer.addHeaderAlias("nickname", "昵称");
writer.addHeaderAlias("email", "邮箱");
writer.addHeaderAlias("phone", "电话");
writer.addHeaderAlias("address", "地址");
writer.addHeaderAlias("createTime", "创建时间");
writer.addHeaderAlias("avatarUrl", "头像");
// 一次性写出list内的对象到excel,使用默认样式,强制输出标题
writer.write(list, true);
// 设置浏览器响应的格式
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
String fileName = URLEncoder.encode("用户信息", "UTF-8");
response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");
ServletOutputStream out = response.getOutputStream();
writer.flush(out, true);
out.close();
writer.close();
}
导入接口
@PostMapping("/import")
public Boolean imp(MultipartFile file) throws Exception {
InputStream inputStream = file.getInputStream();
ExcelReader reader = ExcelUtil.getReader(inputStream);
// 方式1:(推荐) 通过 javabean的方式读取Excel内的对象,但是要求表头必须是英文,跟javabean的属性要对应起来
// List list = reader.readAll(User.class);
// 方式2:忽略表头的中文,直接读取表的内容
List> list = reader.read(1);
List users = CollUtil.newArrayList();
for (List
vue导入
handleExcelimportSuccess() { this.$message.success("导入成功") this.load() } 导入
vue导出
各接口的实现导出 exp() { window.open("http://localhost:9090/user/export") }
不再一一列出,看源码接口实现
统一异常处理,包装类
Result.java
package com.qingge.springboot.common;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
private String code;
private String msg;
private Object data;
public static Result success() {
return new Result(Constants.CODE_200, "", null);
}
public static Result success(Object data) {
return new Result(Constants.CODE_200, "", data);
}
public static Result error(String code, String msg) {
return new Result(code, msg, null);
}
public static Result error() {
return new Result(Constants.CODE_500, "系统错误", null);
}
}
全局异常处理类(GlobalExceptionHandler.java)
package com.qingge.springboot.exception;
import com.qingge.springboot.common.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ServiceException.class)
@ResponseBody
public Result handle(ServiceException se){
return Result.error(se.getCode(), se.getMessage());
}
}
自定义异常类(ServiceException.java)
package com.qingge.springboot.exception;
import lombok.Getter;
@Getter
public class ServiceException extends RuntimeException {
private String code;
public ServiceException(String code, String msg) {
super(msg);
this.code = code;
}
}
JWT
JWT依赖
TokenUtils.javacom.auth0 java-jwt3.10.3
package com.qingge.springboot.utils;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.qingge.springboot.entity.User;
import com.qingge.springboot.service.IUserService;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
@Component
public class TokenUtils {
private static IUserService staticUserService;
@Resource
private IUserService userService;
@PostConstruct
public void setUserService() {
staticUserService = userService;
}
public static String genToken(String userId, String sign) {
return JWT.create().withAudience(userId) // 将 user id 保存到 token 里面,作为载荷
.withExpiresAt(DateUtil.offsetHour(new Date(), 2)) // 2小时后token过期
.sign(Algorithm.HMAC256(sign)); // 以 password 作为 token 的密钥
}
public static User getCurrentUser() {
try {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String token = request.getHeader("token");
if (StrUtil.isNotBlank(token)) {
String userId = JWT.decode(token).getAudience().get(0);
return staticUserService.getById(Integer.valueOf(userId));
}
} catch (Exception e) {
return null;
}
return null;
}
}
token示例
{"username":"admin","password":"admin","nickname":"管理员11111","avatarUrl":"https://img-blog.csdnimg.cn/c6d0ece75d3f4833bd820b8aa2eb952b.png","token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxIiwiZXhwIjoxNjQ0MzgxMDI4fQ.87nwS8ENDOu6RY-4PTLBBzXfDv6-5TiQLQhBXrYGb700"}
JwtInterceptor.java
package com.qingge.springboot.config.interceptor;
import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.qingge.springboot.common.Constants;
import com.qingge.springboot.entity.User;
import com.qingge.springboot.exception.ServiceException;
import com.qingge.springboot.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class JwtInterceptor implements HandlerInterceptor {
@Autowired
private IUserService userService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String token = request.getHeader("token");
// 如果不是映射到方法直接通过
if(!(handler instanceof HandlerMethod)){
return true;
}
// 执行认证
if (StrUtil.isBlank(token)) {
throw new ServiceException(Constants.CODE_401, "无token,请重新登录");
}
// 获取 token 中的 user id
String userId;
try {
userId = JWT.decode(token).getAudience().get(0);
} catch (JWTDecodeException j) {
throw new ServiceException(Constants.CODE_401, "token验证失败,请重新登录");
}
// 根据token中的userid查询数据库
User user = userService.getById(userId);
if (user == null) {
throw new ServiceException(Constants.CODE_401, "用户不存在,请重新登录");
}
// 用户密码加签验证 token
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
try {
jwtVerifier.verify(token); // 验证token
} catch (JWTVerificationException e) {
throw new ServiceException(Constants.CODE_401, "token验证失败,请重新登录");
}
return true;
}
}
InterceptorConfig.java
package com.qingge.springboot.config;
import com.qingge.springboot.config.interceptor.JwtInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor())
.addPathPatterns("export", "import");
}
@Bean
public JwtInterceptor jwtInterceptor() {
return new JwtInterceptor();
}
}
login方法
@Override
public UserDTO login(UserDTO userDTO) {
User one = getUserInfo(userDTO);
if (one != null) {
BeanUtil.copyProperties(one, userDTO, true);
// 设置token
String token = TokenUtils.genToken(one.getId().toString(), one.getPassword());
userDTO.setToken(token);
return userDTO;
} else {
throw new ServiceException(Constants.CODE_600, "用户名或密码错误");
}
}
request.js
let user = localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")) : null
if (user) {
config.headers['token'] = user.token; // 设置请求头
}
// 当权限验证不通过的时候给出提示
if (res.code === '401') {
ElementUI.Message({
message: res.msg,
type: 'error'
});
}
文件上传
接口实现看源码
application.yml
files:
upload:
path: D:大二下学期小白做毕设files
配置拦截器,不需要拦截
前端实现
图标实现
vue安装echarts
npm i echarts -S
Echarts官网:https://echarts.apache.org/zh/index.html
Echarts使用手册:https://echarts.apache.org/handbook/zh/get-started/
引入echarts:import * as echarts from 'echarts'
前端实现看源码 权限菜单讲解 角色管理页面设计sys_role表用代码生成器生成三层架构复制拷贝一份Role.vue (记得增加路由,修改响应字段、功能,使接口一致)
菜单管理页面
设计sys_menu表用代码生成器生成三层架构复制拷贝一份Menu.vue (记得增加路由,修改响应字段、功能,使接口一致)
Unknown column ‘icon’ in ‘field list’ 报错原因是数据库字段不一致,重修修改数据库字段
分配菜单功能
Role.vue 添加分配菜单按钮
在菜单分配表格使用树形控件
修改Menu.vue,使用树形表格(需要用到children,要修改对应entity对象,添加children属性,同时加上@TableFild(exist=false))
@TableField(exist = false)
private List 修改接口,填充children,所以要修改数据库字段,利用id-pid的自关联的关系
@GetMapping
public Result findAll(@RequestParam(defaultValue = "") String name) {
QueryWrapper 修改前端页面去掉分页,添加新增子菜单功能
新增子菜单 handleAdd(pid) { this.dialogFormVisible = true; this.form = {}; this.form.pid=pid; }, save() { // console.log(this.form); request.post("menu", this.form).then(res => { if (res.code=='200') { this.$message.success("保存成功"); this.dialogFormVisible = false; this.load(); } else { this.$message.error("保存失败"); } }) },
修改Role.vue的分配菜单,调用接口数据,记得加上prop属性才能显示数据
完善图标功能显示,新建Dict字典表(name,value,type) 新建entity,mapper
新建接口(查询dict表中所有的icon类型)
@GetMapping("/icons")
public Result icons() {
QueryWrapper queryWrapper = new QueryWrapper<>();
queryWrapper.eq("type", Constants.DICT_TYPE_ICON);
List dictList = this.dictMapper.selectList(queryWrapper);
return Result.success(dictList);
}
Menu.vue 前端下拉框的实现,表格中图标的显示
{{item.name}}
Role.vue 分配菜单中图标的添加,以及菜单的默认展开功能
{{ data.name }} handleMenu(){ this.menuVisible=true; this.request.get("menu").then(res=>{ this.treeData=res.data; this.expanded=res.data.map(v=>v.id); }) }
新建角色菜单关系表sys_role_menu(role_id,menu_id)新建实体类,mapper
写两个接口,先删后增 关于role_menu实体类的增删与查
@GetMapping("roleMenu/{roleId}")
public List findMenuIdByRoleId(@PathVariable("roleId") Integer roleId) {
return roleService.findMenuIdByRoleId(roleId);
}
@PostMapping("roleMenu/{roleId}")
public Result addMenu(@PathVariable("roleId")Integer roleId,@RequestBody List menuIds){
roleService.addMenu(roleId,menuIds);
return Result.success();
}
@Select("select menu_id from sys_role_menu where role_id=#{roleId}")
List findMenuIdByRoleId(@Param("roleId") Integer roleId);
@Override
public void addMenu(Integer roleId, List menuIds) {
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(RoleMenu::getRoleId,roleId);
roleMenuMapper.delete(queryWrapper);
for (Integer menuId : menuIds) {
RoleMenu roleMenu = new RoleMenu();
roleMenu.setMenuId(menuId);
roleMenu.setRoleId(roleId);
roleMenuMapper.insert(roleMenu);
}
}
前端调用,修改树形控件传参数(role_id,Listmenu_id)实现新增关系和菜单默认选择功能
{{ data.name }} handleMenu(id){ this.menuVisible=true; this.request.get("menu").then(res=>{ this.treeData=res.data; this.expanded=res.data.map(v=>v.id); }) this.roleId=id; this.request.get("role/roleMenu/"+this.roleId).then(res=>{ this.checked=res; }) }, saveRoleMenu(){ this.request.post("role/roleMenu/"+this.roleId,this.$refs.tree.getCheckedKeys()).then(res=>{ if(res.code=='200'){ this.$message.success("绑定成功"); this.menuVisible=false; } }) }
修改数据库user表(role) role表(flag)对应实体类也要进行修改
前端User.vue界面表格与表单填加角色信息显示和添加功能,Role.vue添加标识编辑与显示
this.request.get("role").then(res=>{ this.options=res; })
修改UserDto对象(role,List
public Result login(UserDto userDto) {
User user = getUserInfo(userDto);
if(user==null){
throw new ServiceException(Constants.CODE_600,"用户名或密码错误");
}
BeanUtils.copyProperties(user,userDto);
//menus属性填充
String roleFlag = userDto.getRole(); // ROLE_ADMIN
List roleMenu = getRoleMenu(roleFlag);
userDto.setMenus(roleMenu);
String token = TokenUtils.createToken(user.getId().toString(), user.getPassword());
userDto.setToken(token);
return Result.success(userDto);
}
private List getRoleMenu(String roleFlag){
QueryWrapper queryWrapper=new QueryWrapper<>();
queryWrapper.eq("flag",roleFlag);
Integer roleId = roleMapper.selectOne(queryWrapper).getId();
List menuIds = roleMenuMapper.findMenuIdByRoleId(roleId);
//调用menuService查询所有菜单
List allMenu = menuService.findAllMenu("");
// new一个最后筛选完成之后的list
List result=new ArrayList<>();
// 筛选当前用户角色的菜单
for (Menu menu : allMenu) {
if(menuIds.contains(menu.getId())){
result.add(menu);
}
List children = menu.getChildren();
// removeIf() 移除 children 里面不在 menuIds集合中的 元素
children.removeIf(child->!menuIds.contains(child.getId()));
}
return result;
}
修改Aside.vue实现动态菜单
Aside.vue
{{ item.name }}
{{ item.name }}
{{ subItem.name }}
data(){
return {
menus:localStorage.getItem("menus")?JSON.parse(localStorage.getItem("menus")):[]
}
},
Login.vue
login(){
this.$refs['userForm'].validate((valid)=>{
if(valid){//表单校验合法
this.request.post("user/login",this.user).then(res=>{
if(!res.data){
this.$message.error(res.msg);
}else{
this.$router.push("/");
localStorage.setItem("user",JSON.stringify(res.data));
localStorage.setItem("menus",JSON.stringify(res.data.menus));
this.$message.success("登录成功");
}
})
}else{
return false;
}
})
}
修改sys_menu(page_path) 修改实体类 前端页面Menu.vue表格与表单显示页面路径
修改router/index.js 实现动态路由,在login.vue动态设置路由 我实现不了
// 注意:刷新页面会导致页面路由重置
export const setRoutes = () => {
console.log(router.getRoutes())
const storeMenus = localStorage.getItem("menus");
if (storeMenus) {
// 获取当前的路由对象名称数组
const currentRouteNames = router.getRoutes().map(v => v.name);
if (!currentRouteNames.includes('Manage')) {
// 拼装动态路由
const manageRoute = { path: '/', name: 'Manage', component: () => import('../views/Manage.vue'), redirect: "/home", children: [
{ path: 'person', name: '个人信息', component: () => import('../views/Person.vue')},
// { path: 'password', name: '修改密码', component: () => import('../views/Password.vue')}
] }
const menus = JSON.parse(storeMenus)
menus.forEach(item => {
if (item.url) { // 当且仅当path不为空的时候才去设置路由
let itemMenu = { path: item.url.replace("/", ""), name: item.name, component: () => import('../views/' + item.pagePath + '.vue')}
manageRoute.children.push(itemMenu)
} else if(item.children.length) {
item.children.forEach(item => {
if (item.url) {
let itemMenu = { path: item.url.replace("/", ""), name: item.name, component: () => import('../views/' + item.pagePath + '.vue')}
manageRoute.children.push(itemMenu)
}
})
}
})
// 动态添加到现在的路由对象中去
router.addRoute(manageRoute)
}
}
}
setRoutes();
实现404页面
.bgImg {
background: url("../assets/404.jpg") no-repeat;
background-size: 100% 100vh;
}
const routes = [
{
path: ' '../views/404.vue')
}
]
修改添加二级菜单没有一级菜单的bug 在添加的实现中进行二级判断,额外把父菜单也插入进去
public void setRoleMenu(Integer roleId, ListmenuIds) { // QueryWrapper queryWrapper = new QueryWrapper<>(); // queryWrapper.eq("role_id", roleId); // roleMenuMapper.delete(queryWrapper); // 先删除当前角色id所有的绑定关系 roleMenuMapper.deleteByRoleId(roleId); // 再把前端传过来的菜单id数组绑定到当前的这个角色id上去 List menuIdsCopy = CollUtil.newArrayList(menuIds); for (Integer menuId : menuIds) { Menu menu = menuService.getById(menuId); if (menu.getPid() != null && !menuIdsCopy.contains(menu.getPid())) { // 二级菜单 并且传过来的menuId数组里面没有它的父级id // 那么我们就得补上这个父级id RoleMenu roleMenu = new RoleMenu(); roleMenu.setRoleId(roleId); roleMenu.setMenuId(menu.getPid()); roleMenuMapper.insert(roleMenu); menuIdsCopy.add(menu.getPid()); } RoleMenu roleMenu = new RoleMenu(); roleMenu.setRoleId(roleId); roleMenu.setMenuId(menuId); roleMenuMapper.insert(roleMenu); } }
在store/index.js 实现注销方法在管理员分配菜单后触发,重新登录
const store = new Vuex.Store({
state: {
currentPathName: ''
},
mutations: {
setPath (state) {
state.currentPathName = localStorage.getItem("currentPathName")
},
logout(){
localStorage.removeItem("user");
localStorage.removeItem("menus");
router.push("/login")
}
}
})
handleMenu(role){
this.menuVisible=true;
this.request.get("menu").then(res=>{
this.treeData=res.data;
this.expanded=res.data.map(v=>v.id);
})
this.roleId=role.id;
this.roleFlag=role.flag;
this.request.get("role/roleMenu/"+this.roleId).then(res=>{
this.checked=res;
})
},
saveRoleMenu(){
this.request.post("role/roleMenu/"+this.roleId,this.$refs.tree.getCheckedKeys()).then(res=>{
if(res.code=='200'){
this.$message.success("绑定成功");
this.menuVisible=false;
if(this.roleFlag==="SYS_ADMIN"){
this.$store.commit("logout");
}
}
})
// console.log( this.$refs.tree.getCheckedNodes().map(v=>v.id));
}
修改bug:一级菜单选中后全选中二级菜单
handleMenu(role){
this.menuVisible=true;
this.request.get("menu").then(res=>{
this.treeData=res.data;
this.expanded=res.data.map(v=>v.id);
})
this.roleId=role.id;
this.roleFlag=role.flag;
this.request.get("role/roleMenu/"+this.roleId).then(res=>{
this.checked=res;
this.ids.forEach(id=>{
if(!this.checked.includes(id)){
this.$nextTick(() => {
this.$refs.tree.setChecked(id, false)
})
}
})
})
},
this.request.get("menu/ids").then(res=>{
this.ids=res.data;
})
//要额外写个查询所有菜单id的接口
public Result getAllMenu() {
List list = list();
// List ids=new ArrayList<>();
// for (Menu menu : list) {
// Integer id=menu.getId();
// ids.add(id);
// }
List ids = list.stream().map(menu -> menu.getId()).collect(Collectors.toList());
return Result.success(ids);
}
修改bug:一登录直接404 配置路由守卫
{
path: '/404',
name: '404',
component: () => import('../views/404.vue')
},
router.beforeEach((to, from, next) => {
localStorage.setItem("currentPathName", to.name) // 设置当前的路由名称
store.commit("setPath")
// 未找到路由的情况
if (!to.matched.length) {
const storeMenus = localStorage.getItem("menus")
if (storeMenus) {
next("/404")
} else {
// 跳回登录页面
next("/login")
}
}
// 其他的情况都放行
next()
})
修改bug: 个人信息、修改密码的404 设置固定路由
let managerRoute = {path: '/', name: 'Manager',component: () => import('../views/Manage'), redirect: "/home",children: [
{ path: 'person', name: '个人信息', component: () => import('../views/Person.vue')},
// { path: 'password', name: '修改密码', component: () => import('../views/Password.vue')},
修改bug:对于未来元素(还没出现的元素)进行方法调用 1.调整代码顺序,先渲染未来元素再进行对未来元素的方法调用,2. 使用$nextTick处理未来元素,等到这个元素渲染完成之后,再去使用它
selectMenu(role) {
this.roleId = role.id
this.roleFlag = role.flag
// 请求菜单数据
this.request.get("/menu").then(res => {
this.menuData = res.data
// 把后台返回的菜单数据处理成 id数组
this.expends = this.menuData.map(v => v.id)
})
this.request.get("/role/roleMenu/" + this.roleId).then(res => {
this.checks = res.data
// this.menuDialogVis = true
this.ids.forEach(id => {
if (!this.checks.includes(id)) {
// 可能会报错:Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'setChecked')
this.$nextTick(() => {
this.$refs.tree.setChecked(id, false)
})
}
})
this.menuDialogVis = true
})
},
}
修改bug:给管理员新添页面,配菜菜单分配跳到登录后,进新页面报404 提供重置路由想法,让其能再去添加路由
// 提供一个重置路由的方法
export const resetRouter = () => {
router.matcher = new VueRouter({
mode: 'history',
base: process.env.base_URL,
routes
})
}
//登录的时候调用调用这个接口
服务器部署
购买阿里云服务器
重置密码
安装X-shell7远程连接工具,新建会话连接
查看服务器配置(free -h,df -h,top)
开启端口号(安全组)9090、80、3306
服务器中安装软件,安装xftp传输(/home/package)jdk,nginx
利用部署笔记,有道云笔记 安装docker
利用docker安装mysql,在本机Navicat新建连接服务器的数据库
项目修改配置,打包到服务器 要改数据库连接url的ip(用外网ip)
授权mysql,刷新授权
启动服务器中的jar包,测试访问 http://39.108.128.26:9090/swagger-ui/index.html#/
令jar包后台部署
nohup java -jar springboot-0.0.1-SNAPSHOT.jar & ps -ef | grep java 后台查询java进程 kill -9 [进程号] 关闭后台进程
安装 anywhere前端静态资源服务器插件;启动dist目录中的前端项目
npm install anywhere -g
安装nginx(根据笔记安装) 根据服务器ip输入测试nginx安装是否成功(80端口要打开)
配置nginx vue /dist 重启nginx
location / {
root /home/server/dist;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
./nginx -s reload
前端上传和导出localhost修改成服务器外网ip
:action="'http://'+serverIp+':9090/'"后台配置同样配置serverIp 文件上传,导出,接口的使用都需要公网iP
重新打包,重新后台启动
先kill,在重新启动 tailf nohup.out(查看后台日志) npm run build (前端打dist包) cd /usr/local/nginx/sbin ./niginx -s reload 重启nginx申请备案获取自己域名,若是本地访问可以修改C:windowssystem32driversetchosts 后面加ip地址,加网址
常用命令
chmod 777 文件名 --- 修改文件权限 ps -ef | grep java 后台查询实现一对一,一对多,多对多的关联查询
添加课程表course(id,name,score,times-varchar,state-tinyint -1,teacher_id)
直接代码生成
角色修改,生成Course.vue前端页面(可以使用生成器)
写课表对应老师的功能 (多表查询)
@GetMapping("/page") public Result findPage(@RequestParam(defaultValue = "") String name, @RequestParam Integer pageNum, @RequestParam Integer pageSize) { // QueryWrapperqueryWrapper = new QueryWrapper<>(); // queryWrapper.orderByDesc("id"); // if (!"".equals(name)) { // queryWrapper.like("name", name); // } return Result.success(courseService.findPage(new Page<>(pageNum, pageSize), name)); } public Page findPage(Page coursePage,String name) { // Page page = page(coursePage, queryWrapper); // List courseList = page.getRecords(); // for (Course course : courseList) { // course.setTeacher(userService.getById(course.getTeacherId()).getNickname()); // } // return page; return courseMapper.findPage(coursePage,name); } Page findPage(Page coursePage, @RequestParam("name") String name); 老师教授的课程显示按钮,表格文字加el-tag
@GetMapping("/teacherId/{teacherId}") public Result findCourseByTeacherId(@PathVariable("teacherId") Integer teacherId){ QueryWrapperqueryWrapper=new QueryWrapper<>(); queryWrapper.eq("teacher_id",teacherId); List result = courseService.list(queryWrapper); return Result.success(result); } 教授课程 handleCourse(teacherId){ this.courseVis=true; this.request.get("/course/teacherId/"+teacherId).then(res=>{ this.courseData=res.data; }) } 学生选课功能实现(建立多对多关系表,写接口,写前台)
@GetMapping("saveStudentCourse/{studentId}/{courseId}") public Result saveStudentCourse(@PathVariable("studentId")Integer studentId,@PathVariable("courseId")Integer courseId){ courseService.saveStudentCourse(studentId,courseId); return Result.success(); } @Override @Transactional public void saveStudentCourse(Integer studentId, Integer courseId) { courseMapper.deleteStudentCourse(studentId,courseId); courseMapper.saveStudentCourse(studentId,courseId); }insert into student_course values(#{studentId},#{courseId}) delete from student_course where student_id=#{studentId} and course_id=#{courseId} handleSaveCourse(course){ if(!course.state){ this.$message.error("该课程暂时无法选择"); return; } let studentId=this.user.id; this.request.get("/course/saveStudentCourse/"+studentId+"/"+course.id).then(res=>{ if (res.code === '200') { this.$message.success("选课成功") this.load() } else { this.$message.error("选课失败") } }) },实现我的课程查看
@GetMapping("/studentCourse/{studentId}") public Result findCourseByStudentId(@PathVariable("studentId") Integer studentId){ Listresult=courseService.findCourseByStudentId(studentId); return Result.success(result); } @Override public List findCourseByStudentId(Integer studentId) { List courseList = courseMapper.findCourseByStudentId(studentId); for (Course course : courseList) { Integer teacherId = course.getTeacherId(); User teacher = userMapper.selectById(teacherId); course.setTeacher(teacher.getNickname()); } return courseList; } 我的课程 handleMyCourse(){ let studentId=this.user.id; this.request.get("/course/studentCourse/"+studentId).then(res=>{ this.myCourseVis=true; this.courseData=res.data; }) }
前台主页实现
前台布局,页面的实现
跳转路由问题
特殊接口不用权限(自定义注解实现)
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @documented public @interface AuthAccess { } if(!(handler instanceof HandlerMethod)){ return true; }else{ AuthAccess authAccess = ((HandlerMethod) handler).getMethodAnnotation(AuthAccess.class); if(authAccess!=null){ return true; } }springboot集成redis做缓存
添加依赖(springboot-cache)
org.springframework.boot spring-boot-starter-cache添加注解,实现spring cache的集成使用(可根据博客食用)
@PostMapping("/update") @CachePut(value = "files", key = "'frontAll'") public Result update(@RequestBody Files files) { fileMapper.updateById(files); return Result.success(fileMapper.selectList(null)); } @DeleteMapping("/{id}") @CacheEvict(value="files",key="'frontAll'") public Result delete(@PathVariable Integer id) { Files files = fileMapper.selectById(id); files.setIsDelete(true); fileMapper.updateById(files); return Result.success(); } @GetMapping("findAll") @Cacheable(value = "files" ,key = "'frontAll'") public Result findAll(){ ListfilesList = fileMapper.selectList(null); return Result.success(filesList); } 添加依赖(springboot-redis)
org.springframework.boot spring-boot-starter-redis1.4.7.RELEASE 增加配置,使用redis
public Result findAll(){ String str = (String) redisTemplate.opsForValue().get(Constants.REDIS_KEY); List百度地图集成filesList; if(str!=null){ filesList = JSONUtil.toBean(str, new TypeReference >() { }, true); }else{ filesList = fileMapper.selectList(null); String jsonStr = JSONUtil.toJsonStr(filesList); redisTemplate.opsForValue().set(Constants.REDIS_KEY,jsonStr,30,TimeUnit.MINUTES); } return Result.success(filesList); } //清空缓存 public void flushRedis(String key){ redisTemplate.delete(key); }
根据官方文档学习是最好的
申请key值
Hello Word
点标记
//定义事件处理方法 var clickHandler=function(evt){ var lat = evt.latLng.getLat().toFixed(6); var lng = evt.latLng.getLng().toFixed(6); console.log("您点击的的坐标是:"+ lat + "," + lng); } //Map实例创建后,通过on方法绑定点击事件 map.on("click",clickHandler) var markerLayer = new TMap.MultiMarker({ map: map, //指定地图容器 //样式定义 styles: { //创建一个styleId为"myStyle"的样式(styles的子属性名即为styleId) "myStyle": new TMap.MarkerStyle({ "width": 25, // 点标记样式宽度(像素) "height": 35, // 点标记样式高度(像素) }) }, //点标记数据数组 geometries: [{ "id": "1", //点标记唯一标识,后续如果有删除、修改位置等操作,都需要此id "styleId": 'myStyle', //指定样式id "position": new TMap.LatLng(23.040630,113.370344), //点标记坐标位置 }, { "id": "2", //点标记唯一标识,后续如果有删除、修改位置等操作,都需要此id "styleId": 'myStyle', //指定样式id "position": new TMap.LatLng(23.041616,113.372654), //点标记坐标位置 } ] });折线
//创建 MultiPolyline var polylineLayer = new TMap.MultiPolyline({ id: 'polyline-layer', //图层唯一标识 map: map,//设置折线图层显示到哪个地图实例中 //折线样式定义 styles: { 'style_blue': new TMap.PolylineStyle({ 'color': '#3777FF', //线填充色 'width': 6, //折线宽度 'borderWidth': 5, //边线宽度 'borderColor': '#FFF', //边线颜色 'lineCap': 'butt' //线端头方式 }), 'style_red': new TMap.PolylineStyle({ 'color': '#CC0000', //线填充色 'width': 6, //折线宽度 'borderWidth': 5, //边线宽度 'borderColor': '#CCC', //边线颜色 'lineCap': 'round' //线端头方式 }) }, //折线数据定义 geometries: [ {//第1条线 'id': 'pl_1',//折线唯一标识,删除时使用 'styleId': 'style_blue',//绑定样式名 'paths': [new TMap.LatLng(23.040630,113.370344), new TMap.LatLng(23.041616,113.372654)] }, ] });信息窗口
//创建InfoWindow实例,并进行初始化 var infowindow=new TMap.InfoWindow({ content:"广州大学", //信息窗口内容 position:new TMap.LatLng(23.040630,113.370344),//显示信息窗口的坐标 map:map }); infowindow.close(); markerLayer.on("click", function () { //设置infoWindow infowindow.open(); //打开信息窗 })Vue集成视频播放修改上传大小限制前台增加Video组件增加跳转界面VideoDetail



