springboot依赖
java版本的指定
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("/**", corsConfiguration); // 4 对接口配置跨域设置
return new CorsFilter(source);
}
}
添加配置文件application.yml
server:
port: 9090
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/qing?serverTimezone=GMT%2b8
username: root
password: 123456
mybatis:
mapper-locations: classpath:mapper/*.xml #扫描所有mybatis的xml文件
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
增删改查接口
UserController.java
package com.qingge.springboot.controller;
import com.qingge.springboot.entity.User;
import com.qingge.springboot.mapper.UserMapper;
import com.qingge.springboot.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserMapper userMapper;
@Autowired
private UserService userService;
// 新增和修改
@PostMapping
public Integer save(@RequestBody User user) {
// 新增或者更新
return userService.save(user);
}
// 查询所有数据
@GetMapping
public List<User> index() {
List<User> all = userMapper.findAll();
return all;
}
@DeleteMapping("/{id}")
public Integer delete(@PathVariable Integer id) {
return userMapper.deleteById(id);
}
}
UserMapper.java
package com.qingge.springboot.mapper;
import com.qingge.springboot.entity.User;
import org.apache.ibatis.annotations.*;
import java.util.List;
@Mapper
public interface UserMapper {
@Select("SELECT * from sys_user")
List<User> findAll();
@Insert("INSERT into sys_user(username, password,nickname,email,phone,address) VALUES (#{username}, #{password}," +
" #{nickname}, #{email},#{phone}, #{address})")
int insert(User user);
int update(User user);
@Delete("delete from sys_user where id = #{id}")
Integer deleteById(@Param("id") Integer id);
}
User.xml
<mapper namespace="com.qingge.springboot.mapper.UserMapper">
<update id="update">
update sys_user
<set>
<if test="username != null">
username = #{username},
if>
<if test="nickname != null">
nickname = #{nickname},
if>
<if test="email != null">
email = #{email},
if>
<if test="phone != null">
phone = #{phone},
if>
<if test="address != null">
address = #{address}
if>
set>
<where>
id = #{id}
where>
update>
mapper>
根据官方文档快速入门
添加mybatis-plus依赖
com.baomidou
mybatis-plus-boot-starter
3.5.1
mybatis-plus 配置(MybatisPlusConfig.java)
package com.qingge.springboot.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("com.qingge.springboot.mapper")
public class MybatisPlusConfig {
// 最新版
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
修改application.yml
server:
port: 9090
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/qing?serverTimezone=GMT%2b8
username: root
password: 123456
mybatis:
mapper-locations: classpath:mapper/*.xml #扫描所有mybatis的xml文件
# configuration:
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
User.java
package com.qingge.springboot.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
@Data
@TableName(value = "sys_user")
public class User {
@TableId(type = IdType.AUTO)
private Integer id;
private String username;
@JsonIgnore
private String password;
private String nickname;
private String email;
private String phone;
private String address;
@TableField(value = "avatar_url") // 指定数据库的字段名称
private String avatar;
}
访问地址:http://localhost:9090/swagger-ui/index.html
SwaggerConfig.java
package com.qingge.springboot.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
@Configuration
@EnableOpenApi
public class SwaggerConfig {
/**
* 创建API应用
* apiInfo() 增加API相关信息
* 通过select()函数返回一个ApiSelectorBuilder实例,用来控制哪些接口暴露给Swagger来展现,
* 本例采用指定扫描的包路径来定义指定要建立API的目录。
*
* @return
*/
@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();
}
/**
* 创建该API的基本信息(这些基本信息会展现在文档页面中)
* 访问地址:http://ip:port/swagger-ui.html
*
* @return
*/
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", "[email protected]"))
.version(version)
.build();
}
}
pom.xml
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-boot-starterartifactId>
<version>3.0.0version>
dependency>
npm i axios -S
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-generator
3.5.1
org.apache.velocity
velocity
1.7
代码生成 CodeGenerator
package com.qingge.springboot.utils;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import java.util.Collections;
/**
* mp代码生成器
* by 青哥哥
* @since 2022-01-26
*/
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
/**
*
* $!{table.comment} 前端控制器
*
*
* @author ${author}
* @since ${date}
*/
#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<Integer> 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<User> 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-all
5.7.20
org.apache.poi
poi-ooxml
4.1.2
导出接口 起别名可在实体类用@Alias实现
/**
* 导出接口
*/
@GetMapping("/export")
public void export(HttpServletResponse response) throws Exception {
// 从数据库查询出所有的数据
List<User> 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();
}
导入接口
/**
* excel 导入
* @param file
* @throws Exception
*/
@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<Object>> list = reader.read(1);
List<User> users = CollUtil.newArrayList();
for (List<Object> row : list) {
User user = new User();
user.setUsername(row.get(0).toString());
user.setPassword(row.get(1).toString());
user.setNickname(row.get(2).toString());
user.setEmail(row.get(3).toString());
user.setPhone(row.get(4).toString());
user.setAddress(row.get(5).toString());
user.setAvatarUrl(row.get(6).toString());
users.add(user);
}
userService.saveBatch(users);
return true;
}
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 {
/**
* 如果抛出的的是ServiceException,则调用该方法
* @param se 业务异常
* @return Result
*/
@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;
}
}
com.auth0
java-jwt
3.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;
}
/**
* 生成token
*
* @return
*/
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 的密钥
}
/**
* 获取当前登录的用户信息
*
* @return user对象
*/
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;
}
}
{"username":"admin","password":"admin","nickname":"管理员11111","avatarUrl":"https://img-blog.csdnimg.cn/c6d0ece75d3f4833bd820b8aa2eb952b.png","token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxIiwiZXhwIjoxNjQ0MzgxMDI4fQ.87nwS8ENDOu6RY-4PTLBBzXfDv6-5TiQLQhBXrYGb700"}
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;
}
}
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("/**") // 拦截所有请求,通过判断token是否合法来决定是否需要登录
.excludePathPatterns("/user/login", "/user/register", "/**/export", "/**/import");
}
@Bean
public JwtInterceptor jwtInterceptor() {
return new JwtInterceptor();
}
}
@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, "用户名或密码错误");
}
}
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'
});
}
files:
upload:
path: D:\大二下学期\小白做毕设\files\
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'
Unknown column ‘icon’ in ‘field list’ 报错原因是数据库字段不一致,重修修改数据库字段
Role.vue 添加分配菜单按钮
在菜单分配表格使用树形控件
修改Menu.vue,使用树形表格(需要用到children,要修改对应entity对象,添加children属性,同时加上@TableFild(exist=false))
@TableField(exist = false)
private List<Menu> children;
@TableField("pid")
private Integer pid;
修改接口,填充children,所以要修改数据库字段,利用id-pid的自关联的关系
@GetMapping
public Result findAll(@RequestParam(defaultValue = "") String name) {
QueryWrapper<Menu> queryWrapper = new QueryWrapper<>();
if(StrUtil.isNotBlank(name)){
queryWrapper.like("name",name);
}
//查询所有菜单
List<Menu> list = menuService.list(queryWrapper);
//查询父菜单
List<Menu> parentMenu = list.stream().filter(menu -> (menu.getPid() == null)).collect(Collectors.toList());
for (Menu menu : parentMenu) {
//为每个父菜单封装二级菜单
List<Menu> childrenList = list.stream().filter(m -> menu.getId().equals(m.getPid())).collect(Collectors.toList());
menu.setChildren(childrenList);
}
return Result.success(parentMenu);
}
修改前端页面去掉分页,添加新增子菜单功能
新增子菜单
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<Dict> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("type", Constants.DICT_TYPE_ICON);
List<Dict> 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<Integer> findMenuIdByRoleId(@PathVariable("roleId") Integer roleId) {
return roleService.findMenuIdByRoleId(roleId);
}
@PostMapping("roleMenu/{roleId}")
public Result addMenu(@PathVariable("roleId")Integer roleId,@RequestBody List<Integer> menuIds){
roleService.addMenu(roleId,menuIds);
return Result.success();
}
@Select("select menu_id from sys_role_menu where role_id=#{roleId}")
List<Integer> findMenuIdByRoleId(@Param("roleId") Integer roleId);
@Override
public void addMenu(Integer roleId, List<Integer> menuIds) {
LambdaQueryWrapper<RoleMenu> 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<Menu> roleMenu = getRoleMenu(roleFlag);
userDto.setMenus(roleMenu);
String token = TokenUtils.createToken(user.getId().toString(), user.getPassword());
userDto.setToken(token);
return Result.success(userDto);
}
private List<Menu> getRoleMenu(String roleFlag){
QueryWrapper<Role> queryWrapper=new QueryWrapper<>();
queryWrapper.eq("flag",roleFlag);
Integer roleId = roleMapper.selectOne(queryWrapper).getId();
List<Integer> menuIds = roleMenuMapper.findMenuIdByRoleId(roleId);
//调用menuService查询所有菜单
List<Menu> allMenu = menuService.findAllMenu("");
// new一个最后筛选完成之后的list
List<Menu> result=new ArrayList<>();
// 筛选当前用户角色的菜单
for (Menu menu : allMenu) {
if(menuIds.contains(menu.getId())){
result.add(menu);
}
List<Menu> 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页面
const routes = [
{
path: '/*'
name: '404',
component: () => import(/* webpackChunkName: "about" */ '../views/404.vue')
}
]
修改添加二级菜单没有一级菜单的bug 在添加的实现中进行二级判断,额外把父菜单也插入进去
public void setRoleMenu(Integer roleId, List<Integer> menuIds) {
// QueryWrapper queryWrapper = new QueryWrapper<>();
// queryWrapper.eq("role_id", roleId);
// roleMenuMapper.delete(queryWrapper);
// 先删除当前角色id所有的绑定关系
roleMenuMapper.deleteByRoleId(roleId);
// 再把前端传过来的菜单id数组绑定到当前的这个角色id上去
List<Integer> 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<Menu> list = list();
// List ids=new ArrayList<>();
// for (Menu menu : list) {
// Integer id=menu.getId();
// ids.add(id);
// }
List<Integer> 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:windows\system32\drivers\etc\hosts 后面加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) {
// QueryWrapper queryWrapper = new QueryWrapper<>();
// queryWrapper.orderByDesc("id");
// if (!"".equals(name)) {
// queryWrapper.like("name", name);
// }
return Result.success(courseService.findPage(new Page<>(pageNum, pageSize), name));
}
public Page<Course> findPage(Page<Course> 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<Course> findPage(Page<Course> coursePage, @RequestParam("name") String name);
<select id="findPage" resultType="com.qingge.springboot.entity.Course">
SELECT course.* , user.nickname as teacher
FROM `sys_course` course
LEFT JOIN sys_user user
on course.teacher_id=user.id
<where>
<if test="name!=null and name !=''">
and name like concat('%',#{name},'%')
if>
where>
select>
老师教授的课程显示按钮,表格文字加el-tag
@GetMapping("/teacherId/{teacherId}")
public Result findCourseByTeacherId(@PathVariable("teacherId") Integer teacherId){
QueryWrapper<Course> queryWrapper=new QueryWrapper<>();
queryWrapper.eq("teacher_id",teacherId);
List<Course> 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 id="saveStudentCourse">
insert into student_course values(#{studentId},#{courseId})
</insert>
<delete id="deleteStudentCourse">
delete from student_course where student_id=#{studentId} and course_id=#{courseId}
</delete>
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){
List<Course> result=courseService.findCourseByStudentId(studentId);
return Result.success(result);
}
@Override
public List<Course> findCourseByStudentId(Integer studentId) {
List<Course> courseList = courseMapper.findCourseByStudentId(studentId);
for (Course course : courseList) {
Integer teacherId = course.getTeacherId();
User teacher = userMapper.selectById(teacherId);
course.setTeacher(teacher.getNickname());
}
return courseList;
}
<select id="findCourseByStudentId" resultType="com.qingge.springboot.entity.Course">
SELECT course.*
from sys_course course
LEFT JOIN student_course sc
on sc.course_id=course.id
LEFT JOIN sys_user user
on sc.student_id=user.id
WHERE sc.student_id=#{studentId}
</select>
我的课程
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-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(){
List<Files> filesList = fileMapper.selectList(null);
return Result.success(filesList);
}
添加依赖(springboot-redis)
org.springframework.boot
spring-boot-starter-redis
1.4.7.RELEASE
增加配置,使用redis
public Result findAll(){
String str = (String) redisTemplate.opsForValue().get(Constants.REDIS_KEY);
List<Files> filesList;
if(str!=null){
filesList = JSONUtil.toBean(str, new TypeReference<List<Files>>() {
}, 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(); //打开信息窗
})