若依(前后端分离版)
1.1 什么是若依?
一套权限管理的开源项目
- 用,减少自己的工作量
- 学习优秀开源项目的底层编程思想,设计思路,提高自己的编程能力
1.2登录逻辑
1.2.1 生成验证码
基本思路
1+1=?@2
1+1=?转成图片存入前端,
2存入redis 前端通过 unid 来拿到后端的值
http://localhost/dev-api/captcaImage 前端请求
它做了一个反向管理,进行代理,映射到后端,解决跨域问题
devServer: {
host: '0.0.0.0',
//如果希望外部网络访问就需要制定如下 localhost 是127.0.0.1 专供自己访问,告诉服务器监听0.0.0.0意味着让服务器监听每一个端口
port: port,
open: true,
proxy: {
// detail: https://cli.vuejs.org/config/#devserver-proxy
[process.env.VUE_APP_BASE_API]: {
target: `http://localhost:8080`,
changeOrigin: true,
pathRewrite: {
['^' + process.env.VUE_APP_BASE_API]: ''
//将 /dev-api 替换成 ''再映射到 8080 端口
}
}
},
disableHostCheck: true
},
验证码后端逻辑
@GetMapping("/captchaImage")
public AjaxResult getCode(HttpServletResponse response) throws IOException
{
AjaxResult ajax = AjaxResult.success();
// 查看验证码的开头是否开启
boolean captchaOnOff = configService.selectCaptchaOnOff();
ajax.put("captchaOnOff", captchaOnOff);
if (!captchaOnOff)
{
return ajax;
}
// 保存验证码信息生成 uuid
String uuid = IdUtils.simpleUUID();
String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
String capStr = null, code = null;
BufferedImage image = null;
// 生成验证码
if ("math".equals(captchaType))
{
String capText = captchaProducerMath.createText();
capStr = capText.substring(0, capText.lastIndexOf("@"));
code = capText.substring(capText.lastIndexOf("@") + 1);
image = captchaProducerMath.createImage(capStr);
}
else if ("char".equals(captchaType))
{
capStr = code = captchaProducer.createText();
image = captchaProducer.createImage(capStr);
}
//存入 将这个答案存入 redis
redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
// 转换流信息写出
FastByteArrayOutputStream os = new FastByteArrayOutputStream();
try
{
ImageIO.write(image, "jpg", os);
}
catch (IOException e)
{
return AjaxResult.error(e.getMessage());
}
ajax.put("uuid", uuid);
ajax.put("img", Base64.encode(os.toByteArray()));
return ajax;
}
使用kaptcha工具包进行生成
1.2.2 登录的流程
- 校验验证码
- 校验用户名与密码
- 生成 token
使用异步任务管理器结合线程池,实现了异步操作日志记录,和业务逻辑实现解耦合
boolean captchaOnOff = configService.selectCaptchaOnOff();
// 验证码开关
if (captchaOnOff)
{
validateCaptcha(username, code, uuid);
}
// 用户验证
Authentication authentication = null;
try
{
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
authentication = authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(username, password));
}
catch (Exception e)
{
if (e instanceof BadCredentialsException)
{
//异步生成日志 先是生成一个任务丢到线程池内,由线程池分配一个线程让它来执行
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
throw new UserPasswordNotMatchException();
}
else
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
throw new ServiceException(e.getMessage());
}
}
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
// 将每次的登录记录,记录在 login_info数据库 里面
recordLoginInfo(loginUser.getUserId());
// 生成token
return tokenService.createToken(loginUser);
1.2.3 getInfo
获取当前用户的角色与权限信息,存储到 vuex 中
前端
在 permission 内进行发送请求
router.beforeEach((to, from, next) => {
NProgress.start()
if (getToken()) {
to.meta.title && store.dispatch('settings/setTitle', to.meta.title)
/* has token*/
if (to.path === '/login') {
next({ path: '/' })
NProgress.done()
} else {
if (store.getters.roles.length === 0) {
// 判断当前用户是否已拉取完user_info信息
// dispatch:含有异步操作,数据提交至 actions ,可用于向后台提交数据
//commit:同步操作,数据提交至 mutations ,可用于登录成功后读取用户信息写到缓存里
store.dispatch('GetInfo').then(() => {
store.dispatch('GenerateRoutes').then(accessRoutes => {
// 根据roles权限生成可访问的路由表
router.addRoutes(accessRoutes) // 动态添加可访问路由表
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
})
}).catch(err => {
store.dispatch('LogOut').then(() => {
Message.error(err)
next({ path: '/' })
})
})
} else {
next()
}
}
} else {
// 没有token
if (whiteList.indexOf(to.path) !== -1) {
// 在免登录白名单,直接进入
next()
} else {
next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
NProgress.done()
}
}
})
后端逻辑
@GetMapping("getInfo")
public AjaxResult getInfo()
{
SysUser user = SecurityUtils.getLoginUser().getUser();
// 角色集合
Set<String> roles = permissionService.getRolePermission(user);
// 权限集合
Set<String> permissions = permissionService.getMenuPermission(user);//查是什么角色然后拿到权限 如果是 admin 用户直接给所有的权限如果是其它用户就需要查 user_role 与 role 与 user 表一起拿到用户的具体的权限
AjaxResult ajax = AjaxResult.success();
ajax.put("user", user);
ajax.put("roles", roles);
ajax.put("permissions", permissions);
return ajax;
}
1.2.4 getRouters
根据当前权限获取动态路由
@GetMapping("getRouters")
public AjaxResult getRouters()
{
Long userId = SecurityUtils.getUserId();
List<SysMenu> menus = menuService.selectMenuTreeByUserId(userId);
return AjaxResult.success(menuService.buildMenus(menus));
}
在 select MenuTreeByUserId 中最后调用了getChildPerms 这个方法,执行递归返回一个多级菜单的对象给前端
public List<SysMenu> getChildPerms(List<SysMenu> list, int parentId)
{
List<SysMenu> returnList = new ArrayList<SysMenu>();
for (Iterator<SysMenu> iterator = list.iterator(); iterator.hasNext();)
{
SysMenu t = (SysMenu) iterator.next();
// 一、根据传入的某个父节点ID,遍历该父节点的所有子节点
if (t.getParentId() == parentId)
{
recursionFn(list, t);//用子结点看它有没有子结点
returnList.add(t);
}
}
return returnList;
}
private void recursionFn(List<SysMenu> list, SysMenu t)
{
// 得到子节点列表
List<SysMenu> childList = getChildList(list, t);
t.setChildren(childList);
for (SysMenu tChild : childList)
{
if (hasChild(list, tChild))
{
recursionFn(list, tChild);
}
}
}
1.3用户管理
1.3.1.list
流程: 加载 vue 页面->请求后台数据
@PreAuthorize("@ss.hasPermi('system:user:list')")//对其进行权限控制
@GetMapping("/list")
public TableDataInfo list(SysUser user)
{
startPage();//在这里用 pageHelper 进行将查询语句拦截进行封装分页
List<SysUser> list = userService.selectUserList(user);
return getDataTable(list);
}
@Override
@DataScope(deptAlias = "d", userAlias = "u") //查询的时候给表设置别名
public List<SysUser> selectUserList(SysUser user)
{
return userMapper.selectUserList(user);
}
protected void startPage()
{
PageDomain pageDomain = TableSupport.buildPageRequest();
Integer pageNum = pageDomain.getPageNum();
Integer pageSize = pageDomain.getPageSize();
if (StringUtils.isNotNull(pageNum) && StringUtils.isNotNull(pageSize))
{
String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
Boolean reasonable = pageDomain.getReasonable();
PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable);
//这里的 reasonable 是对参数进行逻辑处理,保证参数的正确性
}
}
1.3.2 treeselect
-
查出所有的部门数据
-
组装成一个数组
@GetMapping("/treeselect") public AjaxResult treeselect(SysDept dept) { List<SysDept> depts = deptService.selectDeptList(dept); return AjaxResult.success(deptService.buildDeptTreeSelect(depts)); }
1.3.3 查询数据
// 节点单击事件
handleNodeClick(data) {
this.queryParams.deptId = data.id;
this.getList();
},
然后发请求给后端,后端返回接口
1.3.4 新增按钮
前端
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.getTreeselect();//getUser 是获取用户后台信息
getUser().then(response => {
this.postOptions = response.posts;
this.roleOptions = response.roles;
this.open = true;
this.title = "添加用户";
this.form.password = this.initPassword;
});
},
后端
userId 是如果有 userId 就是修改,如果没有 userId 就是创建
@PreAuthorize("@ss.hasPermi('system:user:query')")
@GetMapping(value = { "/", "/{userId}" })
public AjaxResult getInfo(@PathVariable(value = "userId", required = false) Long userId)
{
userService.checkUserDataScope(userId);
AjaxResult ajax = AjaxResult.success();
List<SysRole> roles = roleService.selectRoleAll();
ajax.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList()));
ajax.put("posts", postService.selectPostAll());
if (StringUtils.isNotNull(userId))
{
ajax.put(AjaxResult.DATA_TAG, userService.selectUserById(userId));
ajax.put("postIds", postService.selectPostListByUserId(userId));
ajax.put("roleIds", roleService.selectRoleListByUserId(userId));
}
return ajax;
}
@PreAuthorize("@ss.hasPermi('system:user:add')")
@Log(title = "用户管理", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@Validated @RequestBody SysUser user)
{
if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(user.getUserName())))
{
return AjaxResult.error("新增用户'" + user.getUserName() + "'失败,登录账号已存在");
}
else if (StringUtils.isNotEmpty(user.getPhonenumber())
&& UserConstants.NOT_UNIQUE.equals(userService.checkPhoneUnique(user)))
{
return AjaxResult.error("新增用户'" + user.getUserName() + "'失败,手机号码已存在");
}
else if (StringUtils.isNotEmpty(user.getEmail())
&& UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user)))
{
return AjaxResult.error("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在");
}
user.setCreateBy(getUsername());
user.setPassword(SecurityUtils.encryptPassword(user.getPassword()));
return toAjax(userService.insertUser(user));
}
@Override
@Transactional
public int insertUser(SysUser user)
{
// 新增用户信息
int rows = userMapper.insertUser(user);
// 新增用户岗位关联
insertUserPost(user);
// 新增用户与角色管理
insertUserRole(user);
return rows;
}
1.3.5 更改角色
前端
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
this.getTreeselect();
const userId = row.userId || this.ids;
getUser(userId).then(response => {
this.form = response.data;
this.postOptions = response.posts;
this.roleOptions = response.roles;
this.form.postIds = response.postIds;
this.form.roleIds = response.roleIds;
this.open = true;
this.title = "修改用户";
this.form.password = "";
});
},
@PreAuthorize("@ss.hasPermi('system:user:edit')")
@Log(title = "用户管理", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@Validated @RequestBody SysUser user)
{
userService.checkUserAllowed(user);
if (StringUtils.isNotEmpty(user.getPhonenumber())
&& UserConstants.NOT_UNIQUE.equals(userService.checkPhoneUnique(user)))
{
return AjaxResult.error("修改用户'" + user.getUserName() + "'失败,手机号码已存在");
}
else if (StringUtils.isNotEmpty(user.getEmail())
&& UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user)))
{
return AjaxResult.error("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在");
}
user.setUpdateBy(getUsername());
return toAjax(userService.updateUser(user));
}
}
数据库:
- 修改User
- 重新维护它的 user_post 和 user_role
@Override
@Transactional 添加了事务如果出现了异常就会直接回滚了
public int updateUser(SysUser user)
{
Long userId = user.getUserId();
// 删除用户与角色关联
userRoleMapper.deleteUserRoleByUserId(userId);
// 新增用户与角色管理
insertUserRole(user);
// 删除用户与岗位关联
userPostMapper.deleteUserPostByUserId(userId);
// 新增用户与岗位管理
insertUserPost(user);
return userMapper.updateUser(user);
}
1.3.6 删除数据
前端
/** 删除按钮操作 */
handleDelete(row) {
const userIds = row.userId || this.ids;
this.$modal.confirm('是否确认删除用户编号为"' + userIds + '"的数据项?').then(function() {
return delUser(userIds);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
后端
/**
* 删除用户
*/
@PreAuthorize("@ss.hasPermi('system:user:remove')")
@Log(title = "用户管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{userIds}")
public AjaxResult remove(@PathVariable Long[] userIds)
{
if (ArrayUtils.contains(userIds, getUserId()))//判断当前用户是否在要删除的用户中,如果在就抛异常
{
return error("当前用户不能删除");
}
return toAjax(userService.deleteUserByIds(userIds));//开始删除
}
/**
* 批量删除用户信息
*
* @param userIds 需要删除的用户ID
* @return 结果
*/
@Override
@Transactional
public int deleteUserByIds(Long[] userIds)
{
for (Long userId : userIds)
{
checkUserAllowed(new SysUser(userId));
}
// 删除用户与角色关联
userRoleMapper.deleteUserRole(userIds);
// 删除用户与岗位关联
userPostMapper.deleteUserPost(userIds);
return userMapper.deleteUserByIds(userIds);// 这个并不是真正的删除只是逻辑删除,就是在数据库中有一个字段表明它是否被删除了
}
<delete id="deleteUserByIds" parameterType="Long">
update sys_user set del_flag = '2' where user_id in
<foreach collection="array" item="userId" open="(" separator="," close=")">
#{userId}
</foreach>
<!--> 可以看到其中只是改了一个字段 将 del_flag 置为2<--->
</delete>
1.4 异步任务管理器
//异步生成日志 先是生成一个任务丢到线程池内,由线程池分配一个线程让它来执行
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
throw new UserPasswordNotMatchException();
/**
* 单例模式属于 饿汉式即上来就创建了一个 对象也不管你用不用
*/
private AsyncManager(){}
private static AsyncManager me = new AsyncManager();
public static AsyncManager me()
{
return me;
}
excute 函数需要传入一个 Task 对象 Task 类实现了Runable 接口,是一个任务由线程Thread 去执行
/**
* 记录登录信息
*
* @param username 用户名
* @param status 状态
* @param message 消息
* @param args 列表
* @return 任务task
*/
public static TimerTask recordLogininfor(final String username, final String status, final String message,
final Object... args)
{
final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
final String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
return new TimerTask()
{
@Override
public void run()
{
String address = AddressUtils.getRealAddressByIP(ip);
StringBuilder s = new StringBuilder();
s.append(LogUtils.getBlock(ip));
s.append(address);
s.append(LogUtils.getBlock(username));
s.append(LogUtils.getBlock(status));
s.append(LogUtils.getBlock(message));
// 打印信息到日志
sys_user_logger.info(s.toString(), args);
// 获取客户端操作系统
String os = userAgent.getOperatingSystem().getName();
// 获取客户端浏览器
String browser = userAgent.getBrowser().getName();
// 封装对象
SysLogininfor logininfor = new SysLogininfor();
logininfor.setUserName(username);
logininfor.setIpaddr(ip);
logininfor.setLoginLocation(address);
logininfor.setBrowser(browser);
logininfor.setOs(os);
logininfor.setMsg(message);
// 日志状态
if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER))
{
logininfor.setStatus(Constants.SUCCESS);
}
else if (Constants.LOGIN_FAIL.equals(status))
{
logininfor.setStatus(Constants.FAIL);
}
// 插入数据
SpringUtils.getBean(ISysLogininforService.class).insertLogininfor(logininfor);
}
};
}
封装了登录用户的信息,执行添加操作,这里并不会执行而是将任务交给线程对象来执行.
异步任务管理器,内部定义了一个线程池,然后根据业务创建添加日志的任务,解耦合,日志全部统一处理
@Bean(name = "scheduledExecutorService")
protected ScheduledExecutorService scheduledExecutorService()
{
return new ScheduledThreadPoolExecutor(corePoolSize,
new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build())
{
@Override
protected void afterExecute(Runnable r, Throwable t)
{
super.afterExecute(r, t);
Threads.printException(r, t);
}
};
}
1.5 代码自动生成
分别在admin src 与 ui src 目录下复制粘贴然后直接再 rebuild 一下项目实现生成修改操作
前后端之间使用 jwt 对 token 进行封装,然后这里作个简单的鉴别
what is jwt JSON WEB TOKEN
JWT,为了在网络应用环境间传递声明面执行的一种基于 JSON 的开放标准,可以用来认证也可用于加密。
session
session 保存在服务端内存中的一个对象,主要用来存储所有访问过该服务商的客户端信息,从而保存用户会话状态,但服务端重启用户信息也就消失
token
与 session 的原理大概相同
流程上是这样的:
- 用户使用用户名密码来请求服务器
- 服务器进行验证用户的信息
- 服务器通过验证发送给用户一个token
- 客户端存储token,并在每次请求时附送上这个token值
- 服务端验证token值,并返回数据
这个token必须要在每次请求时传递给服务端,它应该保存在请求头里, 另外,服务端要支持跨域。
jwt 长什么样
jwt 本身并不安全,依赖的是 https 协议
jwt 由三段信息构成 头部,载荷,签证
三段都会进行 base64 进行加密
将这三部分用.
连接成一个完整的字符串,构成了最终的jwt:
签证
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
- header (base64后的)
- payload (base64后的)
- secret
这个部分需要base64加密后的header和base64加密后的payload使用.
连接组成的字符串,然后通过header中声明的加密方式进行加盐secret
组合加密,然后就构成了jwt的第三部分。
导入 excel
pol与easy excel讲解
常用场景
- 将用户信息导出为 excel
- 将 excel 表中的信息导入到网站数据库
使用
// 1. 创建一个工作薄 03版本
Workbook workbook = new HSSFWorkbook();
// 2. 创建一个工作表
Sheet sheet = workbook.createSheet("猿创孵化表");
// 3.创建一个行 0代表第一行
Row row = sheet.createRow(0);
// 4.创建一个单元格 0 代表第一个单元格
Cell cell11 = row.createCell(0);
// 5. 设置值
cell11.setCellValue("姓名");
Cell cell13 = row.createCell(2);
Cell cell12 = row.createCell(1);
cell12.setCellValue("班级");
String time = new DateTime().toString("yyyy-MM-dd HH:mm:ss");
cell12.setCellValue(time);
// 获得输出流
FileOutputStream fileOutputStream = new FileOutputStream("猿创算法孵化表.xls");
// 写入位置
workbook.write(fileOutputStream);
// 关闭流
fileOutputStream.close();
System.out.println("文件生成完毕");
// 1. 创建一个工作薄 07 07的使用只有后缀名的不同与类的不同
Workbook workbook = new XSSFWorkbook();
// 2. 创建一个工作表
Sheet sheet = workbook.createSheet("猿创孵化表");
// 3.创建一个行 0代表第一行
Row row = sheet.createRow(0);
// 4.创建一个单元格 0 代表第一个单元格
Cell cell11 = row.createCell(0);
// 5. 设置值
cell11.setCellValue("姓名");
Cell cell13 = row.createCell(2);
Cell cell12 = row.createCell(1);
cell12.setCellValue("班级");
String time = new DateTime().toString("yyyy-MM-dd HH:mm:ss");
cell12.setCellValue(time);
// 获得输出流
FileOutputStream fileOutputStream = new FileOutputStream("猿创算法孵化表07.xlsx");
// 写入位置
workbook.write(fileOutputStream);
// 关闭流
fileOutputStream.close();
System.out.println("文件生成完毕");
大数据量时
03版本的比较快,但不能超过65536行
07版本的比较慢
为了解决07版本比较慢的这一点我们可以使用 sxxfworkbook 来加速这一过程,
它是实现 BigGridDemo 策略的流版本,它允许编写非常大的数据而不用担心内存耗尽
因为在任何时候,只有可配置的一部分保存在内存中
注意
过程中会产生临时文件,需要清理临时文件,默认由100条记录被保存在内存中,如果超过这数量,则最前面的数据被写入临时文件
@Test
public void testWrite03()throws Exception{
long begin = System.currentTimeMillis();
Workbook workbook = new SXSSFWorkbook();
Sheet sheet = workbook.createSheet("猿创狗仔计划");
for (int i = 0; i < 65536; i++) {
Row row = sheet.createRow(i);
for (int j = 0; j < 10; j++) {
Cell cell = row.createCell(j);
cell.setCellValue(j);
}
}
FileOutputStream fileOutputStream = new FileOutputStream("猿创狗仔计划.xlsx");
workbook.write(fileOutputStream);
fileOutputStream.close();
((SXSSFWorkbook) workbook).dispose();
long end = System.currentTimeMillis();
System.out.println(end-begin);
}
工作中狠一点就是表读过来,存入集合,再遍历放入数据库,再根据数据库中的字段在前端解析
读
普通的 poi 太复杂,直接上 easyexcel
导入依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.0.5</version>
</dependency>
// 写法2
String fileName = "Test.xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
// 如果这里想使用03 则 传入excelType参数即可
EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
然后直接调 api 进行
下载文件逻辑
在 rouyi-admin 中的 CommonController 中进行修改,先对后缀名进行合法性验证,然后得到
try
{
if (!FileUtils.checkAllowDownload(fileName))//这里主要对后缀名及非法下载进行处理
{
throw new Exception(StringUtils.format("文件名称({})非法,不允许下载。 ", fileName));
}
String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1);//这里是合成一个生成后的文件名称
String filePath = RuoYiConfig.getDownloadPath() + fileName;//通过读取royi的配置文件得到文件应该存放的位置
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);//设置响应类型
FileUtils.setAttachmentResponseHeader(response, realFileName);//设置响应头
FileUtils.writeBytes(filePath, response.getOutputStream());//这个非常重要大概是通过
if (delete)
{
FileUtils.deleteFile(filePath);
}
}
catch (Exception e)
{
log.error("下载文件失败", e);
}
导入文件是 SysUserController.java 里面的 importData 方法负责
public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception
{
ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class); //获取这个工具类
List<SysUser> userList = util.importExcel(file.getInputStream()); // 将文件读入流放入 在内部进行表格的处理数据封装成一个 list 返回 其中已经写入了文件
String operName = getUsername();
String message = userService.importUser(userList, updateSupport, operName); //对这个list 进行检索并返回消息有多少条成功或者失败
return AjaxResult.success(message);
}
如果我们需要自定义哪些数据可以被下载我们可以在SecurityConfig中进行配置
若依的注销在其中也进行了配置
公共基础
-
前端获取方法:this.$store.state.user.name
-
后端获取方法
-
String username = SecurityUtils.getUsername();
-
-
注解
restController 注解 controller 会使return 的方法不会返回jsp 页面,或者html,配置的html 解析器不起作用,返回的内容就是return 的内容
关于translational 注解只能放在 public 方法上,spring 的默认事务规则是当遇到运行异常与error时会回滚
如果想针对检查异常进行事务回滚,可以在@Transactional
注解里使用 rollbackFor
属性明确指定异常。
并且出现了异常不要试图去用catch去捕获
@Transactional(rollbackFor = Exception.class)
responseBody 将 java 对象转换为 json 格式
@ControllerAdvice 和 @RestControllerAdvice都是对Controller进行增强的,可以全局捕获spring mvc抛的异常。
RestControllerAdvice = ControllerAdvice + ResponseBody
return new ScheduledThreadPoolExecutor(corePoolSize,
new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build())
{
@Override
protected void afterExecute(Runnable r, Throwable t)
{
super.afterExecute(r, t);
Threads.printException(r, t);
}
};
一种新语法即是 new 一个对象的同时,重写它父类的方法
@Aspect
@Pointcut("execution(public * com.rest.module..*.*(..))")
//指定某一个包下的哪一个方法进行切面
public void getMethods() {
}
// 指定注解
@Pointcut("@annotation(com.rest.utils.SysPlatLog)")
//只要加了这个注解我就来切
public void withAnnotationMethods() {
}
3、Advice,在切入点上执行的增强处理,主要有五个注解:
@Before 在切点方法之前执行
@After 在切点方法之后执行
@AfterReturning 切点方法返回后执行
@AfterThrowing 切点方法抛异常执行
@Around 属于环绕增强,能控制切点执行前,执行后
-
@Resource(name = “captchaProducer”)
- @Resource默认按byName自动注入。
- 既不指定name属性,也不指定type属性,则自动按byName方式进行查找。如果没有找到符合的bean,则回退为一个原始类型进行进行查找,如果找到就注入。
- 只是指定了@Resource注解的name,则按name后的名字去bean元素里查找有与之相等的name属性的bean。
- 只指定@Resource注解的type属性,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
-
@PreAuthorize(“@ss.hasPermi(‘monitor:operlog:list’)”)
- @preAuthorize指定用户访问这个接口需要哪一部分权限,进行权限的验证
-
@Value(“spring.port”)可以读取spring 配置文件中配置的值返回的是一个字符串