一.下载layui社区的模版
https://fly.layui.com/store/FlyTemplate/
二.springboot热部署
参考(https://blog.csdn.net/panruola/article/details/87890234)
2.1导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
2.2Compiler,勾选 “Make project automatically”
3.3.快捷键 Shift+Ctrl+Alt+/ ,选择 “Registry” ,选中打勾 “compiler.automake.allow.when.app.running”
三.导入到springboot项目中(抽取片段)
我们可以抽取功能的片段:比如comment.html中公共的css样式(title不一样,我们可以通过参数传过来)
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:fragment="commentStyle">
<meta charset="utf-8">
<title th:text="${title}">基于 layui 的极简社区页面模版</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="keywords" content="fly,layui,前端社区">
<meta name="description" content="Fly社区是模块化前端UI框架Layui的官网社区,致力于为web开发提供强劲动力">
<link rel="stylesheet" th:href="@{/static/layui/css/layui.css}">
<link rel="stylesheet" th:href="@{/static/css/global.css}">
</head>
<body>
引入:
<!--引入公共头部样式-->
<head th:replace="~{inc/comment::commentStyle(title='首页')}">
四.controller中防止非整形访问该URL会报异常,所以通过id:\\d*来只允许整形通过
/*
博客详细页面
id:\d*只允许传的参数为整形
*/
@GetMapping("/post/{id:\\d*}")
public String detail(@PathVariable("id") Long id){
return "post/detail";
}
五.导航栏的信息通过项目启动就存进容器中,这样项目一运行就查询数据库中的数据
1.创建一个类实现ApplicationRunner和ServletContextAware类
- ApplicationRunner用于在项目运行就执行
- ServletContextAware用于获取容器上下文
2.运行项目查询数据库中数据,并存进上下文
package com.wcy.eblog.config;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.wcy.eblog.entity.MCategory;
import com.wcy.eblog.service.MCategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.context.ServletContextAware;
import javax.servlet.ServletContext;
import java.util.List;
/**
* ApplicationRunner 容器在启动的时候就会执行
* ServletContextAware获取上下文容器
* 把分类(导航栏)存进上下文容器中 (便于速度更快)
*/
@Component
public class ContextStartUp implements ApplicationRunner, ServletContextAware {
@Autowired
private MCategoryService mCategoryService;
ServletContext servletContext;
@Override
public void run(ApplicationArguments args) throws Exception {
//查询status=0的所有分类导航栏 若为1则不查询
List<MCategory> mCategoryList = mCategoryService.list(new QueryWrapper<MCategory>().eq("status",0));
//把查询到的数据存进容器中
servletContext.setAttribute("mCategoryList", mCategoryList);
}
@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext=servletContext;
}
}
3.thymeleaf获取容器上下文的方法
<li th:each="category:${#servletContext.getAttribute('mCategoryList')}">
<a th:href="@{'/catalog/'+${category.id}}">[[${category.name}]]</a>
</li>
六.thymeleaf处理时间显示成几小时前
<!--对时间处理成几小时前-->
<span th:datetime="${#dates.format(postVo.created, 'yyyy-MM-dd HH:mm')}" class="time"></span>
<script src="https://cdn.bootcss.com/timeago.js/3.0.2/timeago.js"></script>
<script>
// 自动更新
var timeagoInstance = timeago();// 实例
timeagoInstance.render(document.querySelectorAll('.time'),'zh_CN');
</script>
七.注意layui分页的JS需要等页面加载完毕才执行
问题:防止layui分页消失,不会出现分页按钮
解决:
放到JQ的加载完毕才会执行的方法
$(function(){
});
八.实现博客七天回复量排行榜(redis实现)
1.实现逻辑:
- 查询数据库中前七天的博客以及博客的评论数
- 通过redis的zset有序集合key存关于天数的(博客创建的那一天 有序map),然后zset里面的key存博客id之类的,分数存评论数
比如:2020-7-2日有哪些博客创建了并且评论了,那么就会存进这个有序集合的key里面(见下图)
4. 因为web端需要展示博客的标题、评论数,所有还需要通过一个hash来存储这些数据。
5. 这些都需要设置一个过期时间 超出七天则清除
相关命令:
2.对应的service逻辑方法:
//初始化本周热议
@Override
public void initWeekRank() {
//1.查询七天内的博客
//DateUtil.offset(new Date(), DateField.DAY_OF_MONTH,-7)获取当前时间的第前七天的时间
List<MPost> mPosts = mPostMapper.selectList(new QueryWrapper<MPost>()
.ge("created", DateUtil.offset(new Date(), DateField.DAY_OF_MONTH,-7))
.gt("comment_count",0)//评论数大于0
);
//2.存进redis中
for(MPost mPost:mPosts){
String key="eblog:rank:time:"+DateUtil.format(mPost.getCreated(),"yyyy-MM-dd");
//2.1//存进zset中
redisUtil.zSet(key,mPost.getId(),mPost.getCommentCount());
//2.2.计算当前时间与博客创建时间差 7-(18-14)
long time=(7-DateUtil.between(new Date(),mPost.getCreated(), DateUnit.DAY))*24*60*60;
//2.3设置key消失时间
boolean expire = redisUtil.expire(key, time);
//2.4由于前端需要显示标题、评论数等信息
hashCachePostIdAndTitle(mPost);
}
//3.计算某七天的评论数并逆序排序
zUnionPost7DaysForWeekRand();
}
//计算七天内的评论数
private void zUnionPost7DaysForWeekRand() {
String newKey="eblog:rank:7week";
List<String> list=new ArrayList<>();
for(int i=-7;i<0;i++){
//这七天的key存进list里面 用户求并集
String timeKey="eblog:rank:time:"+ DateUtil.format(DateUtil.offsetDay(new Date(),i),"yyyy-MM-dd");
list.add(timeKey);
}
String key="eblog:rank:time:"+ DateUtil.format(new Date(),"yyyy-MM-dd");
redisUtil.zUnionAndStore(key,list,newKey);//timeKey
}
//把博客信息存进redis中
private void hashCachePostIdAndTitle(MPost mPost) {
//通过博客的ID来存储键
String key="eblog:rank:post:"+mPost.getId();
//计算当前时间与博客创建时间差 7-(18-14)
long time=(7-DateUtil.between(new Date(),mPost.getCreated(), DateUnit.DAY))*24*60*60;
//存进redis中并设置过期时间
redisUtil.hset(key,"id",mPost.getId(),time);
redisUtil.hset(key,"title",mPost.getTitle(),time);
redisUtil.hset(key,"commentCount",mPost.getCommentCount(),time);
}
3.一旦用户评论或删除评论就修改缓存中评论的数目,并且重新更新缓存中的并集以及博客信息
/**
* 增加或减少redis这篇博客的评论数
* @param postId
* @param isincr
*/
@Override
public void incrCommentWeekRank(Long postId, Boolean isincr) {
String key="eblog:rank:time:"+DateUtil.format(new Date(),"yyyy-MM-dd");
redisUtil.zIncrementScore(key,postId,isincr?1:-1);//判断是+1还是-1
zUnionPost7DaysForWeekRand();//重新更新七天的并集排行榜
MPost mPost = this.getById(postId);
this.hashCachePostIdAndTitle(mPost);//重新更新这篇博客信息
}
九.博客访问量
1.比如查看某篇博客就更新缓存的访问量
@GetMapping("/post/{id:\\d*}")
public String detail(@PathVariable("id") Long id,
@RequestParam(name = "current",required = false,defaultValue = "1") Long current
,@RequestParam(name = "size",required = false,defaultValue = "2") Long size){
//返回博客信息
PostVo postVo=mPostService.findPostById(id);
//实时更新缓存中访问量
this.mPostService.putViewCount(postVo);
//返回评论信息 1.分页 2.博客id 3.用户id 4.排序
IPage<CommentVo> commentVoIPage=mCommentService.pageingComment(new Page(current,size),id,null,"created");
req.setAttribute("PostVo",postVo);
req.setAttribute("CommentVo",commentVoIPage);
return "post/detail";
}
2.更新缓存访问量,给实体的访问量重新赋值,这样传回前端访问量就自动修改了
通过标题八缓存的博客信息来获取访问量,无则使用实体的
/**
*更新缓存中访问量
* @param postVo
*/
@Override
public void putViewCount(PostVo postVo) {
//1.从缓存中拿出访问量 如果缓存中没有直接获取实体的访问量
String key="eblog:rank:post:"+postVo.getId();
Integer viewCount = (Integer)redisUtil.hget(key, "viewCount");
if(viewCount!=null){
postVo.setViewCount(viewCount+1);
}else{
postVo.setViewCount(postVo.getViewCount()+1);
}
//2.更新缓存数据
redisUtil.hset(key,"viewCount",postVo.getViewCount());
}
十.redis不能存储Long类型
redis不能存储Long类型,会把Long类型转换为Integer类型,所以如果想要获取到long类型,就需要:
Integer id = (Integer)redisUtil.hget(key, "id");//博客ID
Long idLong = Long.valueOf(id.longValue());
十一.springboot使用定时任务:
1.开启定时任务
//开启定时任务
@EnableScheduling
public class EblogApplication {
public static void main(String[] args) {
SpringApplication.run(EblogApplication.class, args);
System.out.println("http://localhost:8088");
}
}
2.使用 @Scheduled(fixedRate = 60601000)注解 (每一小时执行一次)
参数配置:
fixedRate = 101000 固定每10秒执行一次
fixedDelay = 101000 延迟每10秒执行一次
cron = “0 0 2 * * ?” //每天凌晨两点执行 https://qqe2.com/cron可配置cron
3.cron的更多配置。
每隔5秒执行一次:*/5 * * * * ?
每隔1分钟执行一次:0 */1 * * * ?
每天23点执行一次:0 0 23 * * ?
每天凌晨1点执行一次:0 0 1 * * ?
每月1号凌晨1点执行一次:0 0 1 1 * ?
每月最后一天23点执行一次:0 0 23 L * ?
每周星期天凌晨1点实行一次:0 0 1 ? * L
在26分、29分、33分执行一次:0 26,29,33 * * * ?
每天的0点、13点、18点、21点都执行一次:0 0 0,13,18,21 * * ?
十二.springboot实现定时更新访问量
Set keys = redisTemplate.keys(“eblog:rank:post:*”);//查询有eblog:rank:post:所有key
/**
* 定时更新访问量
*/
@Component
public class ViewCountSyncTask {
@Autowired
private RedisUtil redisUtil;
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Autowired
private MPostService mPostService;
/**
* @Scheduled进行定时任务
* fixedRate = 10*1000 固定每10秒执行一次
* fixedDelay = 10*1000 延迟每10秒执行一次
* cron = "0 0 2 * * ?" //每天凌晨两点执行 https://qqe2.com/cron可配置cron
*
*/
@Scheduled(fixedRate = 60*60*1000)
public void viewCountTast(){
//1.获取缓存中访问量
Set<String> keys = redisTemplate.keys("eblog:rank:post:*");//查询有eblog:rank:post:所有key
List<MPost> posts=new ArrayList<>();
for(String key:keys){
//查看缓存中是否有访问量
if(redisUtil.hHasKey(key,"viewCount")){
Integer id = (Integer)redisUtil.hget(key, "id");//博客ID
Long idLong = Long.valueOf(id.longValue());
Integer viewCount = (Integer)redisUtil.hget(key, "viewCount");//博客访问量
MPost post=new MPost();
post.setId(idLong);
post.setViewCount(viewCount);
posts.add(post);
//更新之后这些访问量缓存里面的数据需要清空(清空的原因:
// 如果不删除 下次执行这个方法就又会查出很多viewCount这个缓存 以至于上方的list会很大
redisUtil.hdel(key,"viewCount");
}
}
//2.判断是否需要更新到数据库
if(posts.isEmpty()) return;
//3.批量更新数据库
boolean isUpdate = mPostService.updateBatchById(posts);
if(isUpdate){
System.out.println("------------------访问量更新数据库成功--------------------");
}
}
}
十三.springboot使用Hibernate Validator校验pojo的某些字段
1.spring-boot-starter-web包里面有hibernate-validator包,不需要引用hibernate validator依赖。
2.比如邮箱验证(不能为空和格式)
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
@Email
@NotBlank(message = "邮箱不能为空")
private String email;
3.添加hibernate validator的工具类
package com.wcy.eblog.util;
import lombok.Data;
import org.hibernate.validator.HibernateValidator;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* Hibernate Validator校验工具类
*/
public class ValidationUtil {
/**
* 开启快速结束模式 failFast (true)
*/
private static Validator validator = Validation.byProvider(HibernateValidator.class).configure().failFast(false).buildValidatorFactory().getValidator();
/**
* 校验对象
*
* @param t bean
* @param groups 校验组
* @return ValidResult
*/
public static <T> ValidResult validateBean(T t,Class<?>...groups) {
ValidResult result = new ValidationUtil().new ValidResult();
Set<ConstraintViolation<T>> violationSet = validator.validate(t,groups);
boolean hasError = violationSet != null && violationSet.size() > 0;
result.setHasErrors(hasError);
if (hasError) {
for (ConstraintViolation<T> violation : violationSet) {
result.addError(violation.getPropertyPath().toString(), violation.getMessage());
}
}
return result;
}
/**
* 校验bean的某一个属性
*
* @param obj bean
* @param propertyName 属性名称
* @return ValidResult
*/
public static <T> ValidResult validateProperty(T obj, String propertyName) {
ValidResult result = new ValidationUtil().new ValidResult();
Set<ConstraintViolation<T>> violationSet = validator.validateProperty(obj, propertyName);
boolean hasError = violationSet != null && violationSet.size() > 0;
result.setHasErrors(hasError);
if (hasError) {
for (ConstraintViolation<T> violation : violationSet) {
result.addError(propertyName, violation.getMessage());
}
}
return result;
}
/**
* 校验结果类
*/
@Data
public class ValidResult {
/**
* 是否有错误
*/
private boolean hasErrors;
/**
* 错误信息
*/
private List<ErrorMessage> errors;
public ValidResult() {
this.errors = new ArrayList<>();
}
public boolean hasErrors() {
return hasErrors;
}
public void setHasErrors(boolean hasErrors) {
this.hasErrors = hasErrors;
}
/**
* 获取所有验证信息
* @return 集合形式
*/
public List<ErrorMessage> getAllErrors() {
return errors;
}
/**
* 获取所有验证信息
* @return 字符串形式
*/
public String getErrors(){
StringBuilder sb = new StringBuilder();
for (ErrorMessage error : errors) {
sb.append(error.getPropertyPath()).append(":").append(error.getMessage()).append(" ");
}
return sb.toString();
}
public void addError(String propertyName, String message) {
this.errors.add(new ErrorMessage(propertyName, message));
}
}
@Data
public class ErrorMessage {
private String propertyPath;
private String message;
public ErrorMessage() {
}
public ErrorMessage(String propertyPath, String message) {
this.propertyPath = propertyPath;
this.message = message;
}
}
}
4.controller层使用
@PostMapping("/regist")
@ResponseBody
public Result doregist( MUser user,String repass,String vercode){
//验证表单信息是否符合
ValidationUtil.ValidResult validResult = ValidationUtil.validateBean(user);
if (validResult.hasErrors()){
return Result.fail(validResult.getErrors());
}
}
二.添加分组检验:如果未传入分组 则不进行验证
1.对应的bean中
/**
* 邮件
*/
@NotBlank(message = "邮箱不能为空",groups = {Update.class,Save.class})
@Email(message = "邮箱格式不正确",groups = {Update.class,Save.class})
private String email;
//保存的时候校验分组
public interface Save {
}
//更新的时候校验分组
public interface Update {
}
2.controller中验证传入分组
ValidationUtil.ValidResult validResult = ValidationUtil.validateBean(user, User.Save.class);
if(validResult.hasErrors()){
return Result.fail(validResult.getErrors());
}
十四.使用MP的插入方法小心主键会使用它自己的生成策略
1.配置文件
mybatis-plus:
mapper-locations: classpath*:/mapper/**Mapper.xml
type-aliases-package: com.wcy.eblog.*
global-config:
db-config:
id-type: auto #使用mybatis-plus的插入时主键使用数据库自增
2.实体类ID上加上@TableId(value = “id”,type = IdType.AUTO)使用数据库的自增
@TableId(value = "id",type = IdType.AUTO)
private Long id;
十五.不小心用了MP的主键生成策略,如何把主键序列重写修改
1,删除原有主键:
ALTER TABLE `table_name` DROP `id`;
2,添加新主键字段:
ALTER TABLE `table_name` ADD `id` INT( 11 ) NOT NULL FIRST;
3,设置新主键:
ALTER TABLE `table_name` MODIFY COLUMN `id` INT( 11 ) NOT NULL AUTO_INCREMENT,ADD PRIMARY KEY(id);
十六.springboot中使用shiro
shirod重要的三部分
- 1.获取subject(用户)
- 2.shiro有自己的session,无http中的session无关
- 3.判断是否认证
- 4.获取token
- 5.是否有某个角色
- 6.是否有某个权限
- 7.注销
1.导入依赖
<!--shiro权限框架-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!--thymeleaf中使用shiro的标签-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
2.自定义XXXRealm 继承AuthorizingRealm用于授权和认证
授权的设置:
@Component
public class AccountRealm extends AuthorizingRealm {
@Autowired
private MUserService mUserService;
//模拟授权 实际应该查库
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
AccountProfile profile= (AccountProfile) principalCollection.getPrimaryPrincipal();
//模拟给用户9赋予Admin权限
if(profile.getId()== 9){
SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
info.addRole("admin");
return info;
}
return null;
}
/**
* 用于用户登录认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token= (UsernamePasswordToken) authenticationToken;
String email = token.getUsername();
//查询数据库进行操作
AccountProfile profile=mUserService.login(token.getUsername(),String.valueOf(token.getPassword()));
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(profile, token.getCredentials(), getName());
return info;
}
}
3.登录的controller
@PostMapping("login")
@ResponseBody
public Result doLogin(String email,String password,String vercode){
if(StringUtils.isEmpty(email)){
return Result.fail("请填写邮箱");
}
if(StringUtils.isEmpty(password)){
return Result.fail("请填写密码");
}
if(StringUtils.isEmpty(vercode)){
return Result.fail("验证码不能为空");
}
String kapchat = (String)req.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY);
if(!vercode.toUpperCase().equals(kapchat.toUpperCase())){
return Result.fail("验证码错误");
}
UsernamePasswordToken token=new UsernamePasswordToken(email, DigestUtil.md5Hex(password));
try{
//使用shiro进行登录
SecurityUtils.getSubject().login(token);
}catch (AuthenticationException e) {
//进行登录会产生下面的异常
if (e instanceof UnknownAccountException) {
return Result.fail("用户不存在");
} else if (e instanceof LockedAccountException) {
return Result.fail("用户被禁用");
} else if (e instanceof IncorrectCredentialsException) {
return Result.fail("密码错误");
} else {
return Result.fail("用户认证失败");
}
}
return Result.success().action("/");
}
4.shiro的配置类
/**
* 类似于拦截器
*/
@Slf4j
@Configuration
public class ShiroConfig {
@Bean
public SecurityManager securityManager(AccountRealm accountRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(accountRealm);
log.info("------------------>securityManager注入成功");
return securityManager;
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
filterFactoryBean.setSecurityManager(securityManager);
// 配置登录的url和登录成功的url
filterFactoryBean.setLoginUrl("/login");
filterFactoryBean.setSuccessUrl("/user/center");
// 配置未授权跳转页面
filterFactoryBean.setUnauthorizedUrl("/error/403");
Map<String, String> hashMap = new LinkedHashMap<>();
// hashMap.put("/res/**", "anon");
//
// hashMap.put("/user/home", "auth");
// hashMap.put("/user/set", "auth");
// hashMap.put("/user/upload", "auth");
// hashMap.put("/user/index", "auth");
// hashMap.put("/user/public", "auth");
// hashMap.put("/user/collection", "auth");
// hashMap.put("/user/mess", "auth");
// hashMap.put("/msg/remove/", "auth");
// hashMap.put("/message/nums/", "auth");
//
// hashMap.put("/collection/remove/", "auth");
// hashMap.put("/collection/find/", "auth");
// hashMap.put("/collection/add/", "auth");
//
// hashMap.put("/post/edit", "auth");
// hashMap.put("/post/submit", "auth");
// hashMap.put("/post/delete", "auth");
// hashMap.put("/post/reply/", "auth");
//
// hashMap.put("/websocket", "anon");
hashMap.put("/login", "anon");
filterFactoryBean.setFilterChainDefinitionMap(hashMap);
return filterFactoryBean;
}
/**
* 用于thymeleaf模板与shiro的标签
* @return
*/
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}
5.thymeleaf使用标签:
1.引入:
<html xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
2.标签:
guest标签
<shiro:guest>
</shiro:guest>
用户没有身份验证时显示相应信息,即游客访问信息。
user标签
<shiro:user>
</shiro:user>
用户已经身份验证/记住我登录后显示相应的信息。
authenticated标签
<shiro:authenticated>
</shiro:authenticated>
用户已经身份验证通过,即Subject.login登录成功,不是记住我登录的。
notAuthenticated标签
<shiro:notAuthenticated>
</shiro:notAuthenticated>
用户已经身份验证通过,即没有调用Subject.login进行登录,包括记住我自动登录的也属于未进行身份验证。
principal标签
<shiro: principal/>
<shiro:principal property="username"/>
相当于((User)Subject.getPrincipals()).getUsername()。
lacksPermission标签
<shiro:lacksPermission name="org:create">
</shiro:lacksPermission>
如果当前Subject没有权限将显示body体内容。
hasRole标签
<shiro:hasRole name="admin">
</shiro:hasRole>
如果当前Subject有角色将显示body体内容。
hasAnyRoles标签
<shiro:hasAnyRoles name="admin,user">
</shiro:hasAnyRoles>
如果当前Subject有任意一个角色(或的关系)将显示body体内容。
lacksRole标签
<shiro:lacksRole name="abc">
</shiro:lacksRole>
如果当前Subject没有角色将显示body体内容。
hasPermission标签
<shiro:hasPermission name="user:create">
</shiro:hasPermission>
如果当前Subject有权限将显示body体内容
十七.thymeleaf中标签的属性上如何shiro标签
问题:
<shiro:principal property=“avatar”/>只能只能作用于标签的内容上,不能作用于属性上。
解决:
1.我们可以随便设置一个div标签,然后把值给div
2.再通过js获取div的值,然后赋值src或href等等标签属性上面
<div style="display: none" id="div_avatar"><shiro:principal property="avatar"/></div>
<img src="" id="img_avatar"/>
<script>
$(function () {
var avatar=$('#div_avatar').html();
$('#img_avatar').attr("src",avatar)
console.log(avatar)
})
</script>
十八.thymeleaf中判断list是否为空
不为空:
th:if="${not #lists.isEmpty(listData)}"
为空:
th:if="${#lists.isEmpty(listData)}"
十九.批量删除文件(忽略后缀)
public static void deleteFileSuff(File file,String fileName){
//批量删除忽略后缀的文件
String[] list = file.list();
for(String temp:list){
String name=temp.substring(0,temp.indexOf("."));
String nameSuffix=temp.substring(temp.indexOf("."));
if(name.equals(fileName)){
File file1=new File(file.getAbsolutePath()+"/"+fileName+nameSuffix);
file1.delete();
}
}
}
二十.springboot上传图片
1.yml配置:${user.dir}是获取容器src路径的绝对路径
#文件下载路径 ${user.dir}可以获取容器即src路径的绝对路径 记得最后的/
file:
upload:
dir: ${user.dir}/upload/
2.配置类;一但发起/upload请求就会映射到${user.dir}/upload/路径下面,我们只需要再返回图片的路径就可以了
@Configuration
public class MyMvcConfig implements WebMvcConfigurer{
@Value("${file.upload.dir}")
private String uploadPath;//获取配置文件的路径 这样可以随时配置图片上传路径
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//资源映射 用于图片显示
registry.addResourceHandler("/upload/**").addResourceLocations("file:"+uploadPath);
}
}
3.比如返回路径为:/upload/user/avatar"+newName 这样前端展示这个url就可以了。
return Result.success("/upload/user/avatar"+newName);
二十二.layui流和layui的模板引擎
一个类似于流的形式的页面
1.首先使用layui的模板引擎定义一个显示的item
其中使用了layui的时间工具类 这里面没法使用thymeleaf的语法
<ul class="mine-view jie-row" id="fabu">
<script id="lpl_fabu" type="text/html" th:inline="javascript">
<li>
<a class="jie-title" th:href="@{/post/post/{{d.id}}}" target="_blank">{{d.title}}</a>
<i>{{layui.util.toDateString(d.created, 'yyyy-MM-dd HH:mm:ss')}}</i>
<a class="mine-edit" href="/jie/edit/8116">编辑</a>
<em>{{d.viewCount}}阅/{{d.commentCount}}答</em>
</li>
</script>
</ul>
2.js的代码
$(function () {
layui.use(['flow','laytpl','util'], function(){
var $ = layui.jquery; //不用额外加载jQuery,flow模块本身是有依赖jQuery的,直接用即可。
var flow = layui.flow;
var laytpl = layui.laytpl;
var util = layui.util;
//流加载的容器
flow.load({
elem: '#fabu', //指定列表容器 放在哪个容器里面
isAuto:false //不自动加载
,done: function(page, next){ //到达临界点(默认滚动触发),触发下一页
var lis = [];
//以jQuery的Ajax请求为例,请求下一页数据(注意:page是从2开始返回)
$.get('/user/publish?page='+page, function(res){
//假设你的列表返回在data集合中 通过layui的循环获取数据
layui.each(res.data.records, function(index, item){
//通过layui的自定义数据模板 lpl_fabu.innerHTML是模板引擎的ID并显示html
laytpl(lpl_fabu.innerHTML).render(item, function(html){
//放进数组中
lis.push(html);
});
});
//执行下一页渲染,第二参数为:满足“加载更多”的条件,即后面仍有分页
//pages为Ajax返回的总页数,只有当前页小于总页数的情况下,才会继续出现加载更多
next(lis.join(''), page < res.data.pages);
});
}
});
});
})
二十三.对于多表查询如果涉及到四个表以上不推荐使用left join
通过内部查询的方式来查询设计多表的查询
SELECT
m.*, (
SELECT
username
FROM
m_user
WHERE
id = m.from_user_id
) AS fromUserName,
(
SELECT
title
FROM
m_post
WHERE
id = m.post_id
) AS blogTitle,
(
SELECT
content
FROM
m_comment
WHERE
id = m.comment_id
) AS commentContent
FROM
m_user_message m
WHERE
to_user_id=#{id}
ORDER BY created DESC
二十四.shiro中对ajax的自定义过滤器
1.自定义过滤器
public class AuthFilter extends UserFilter {
//重写没有登录重定向的方法
@Override
protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
HttpServletRequest httpServletRequest= (HttpServletRequest) request;
//X-Requested-With: XMLHttpRequest
String header = httpServletRequest.getHeader("X-Requested-With");
//1.判断是否是ajax请求
if(header != null && "XMLHttpRequest".equals(header)){
boolean authenticated = SecurityUtils.getSubject().isAuthenticated();
//登录回超时
if(!authenticated){
response.setContentType("application/json;charset=UTF-8");//设置字符集
JSONObject jsonObject=new JSONObject();
jsonObject.put("msg","回话超时,请重新登录");
response.getWriter().print(jsonObject);
}
}else {
super.redirectToLogin(request, response);
}
}
}
2.shiroconfig配置类中注入自定义过滤器bean
//把自己写的过滤器加入到容器中
@Bean
public AuthFilter authFilter(){
return new AuthFilter();
}
3.shiroFilterFactoryBean中配置自己的过滤器
//添加自定义的过滤器
Map filterMap=new HashMap();
//注意不要与内置的过滤器相同了
filterMap.put("auth",authFilter());
filterFactoryBean.setFilters(filterMap);
//查看当前用户是否收藏了该博客 判断是否是ajax请求 ajax自己写一个过滤器 配置自己的过滤器
hashMap.put("/collection/find/", "auth");
hashMap.put("/collection/remove/", "auth");
hashMap.put("/collection/add/", "auth");
二十五.java中使用hutool的断言
有时候在controller中,我们返回博客详情页面,可能出现博客以及不存在,那么我们就可以使用断言,如果不满足条件,就会抛出异常。
比如下方 当postVo==null 就会抛出异常 (注意是相反的哦)
//返回博客信息
PostVo postVo=mPostService.findPostById(id);
Assert.isTrue(postVo!=null,"该文章已被删除");