1.在配置跨域的时候,遗漏了@Configuration注解,导致跨域失败,数据无法显示在页面中
2.在项目开始之前一定要保证数据库里面有值,确保sql语句可用
3.①在mapper.xml中提取的字段名(在sql标签里面的字段名要用,隔开)
②Mapper.xml文件都要放在resourc文件下面,创建的文件必须/作为分隔,不可以使用逗号(,)进行分隔,否则无法扫描到,
④ 用作规范格式的TagVo类没有添加@Data注解 导致另一个报错
-
在处理完显示,最热标签之后 发现bug 在讲页面像下拉的时候,会不断发送请求,循环刷出数据(重复的)其实就是分页插件的配置,没有加@Configration 导致分页失败
-
sql语句中<foreach>标签里面的collection属性一般默认list不用写其他的,要写的话参考
(决解Mybatis传递List集合报错 Available parameters are [collection, list]_烤地瓜的博客-CSDN博客)
再去熟悉下<where>标签
6.记得配置异常处理加上@ResponBody注解 统一返回Result前后端交互格式
7.前面没处理的bug,下拉页面,一直刷出重复的数据,是因为分页出问题了
在config配置里面 分页插件遗漏了@Bean注解,导致分页插件失效,
以后记住配置的方法名如果没有提亮,就是无效,所有的方法都是
8.up主给的sql里面create_date 为bigint 13位,直接year()不行,需要先转date型后year()。 select year(FROM_UNIXTIME(create_date/1000)) year,month(FROM_UNIXTIME(create_date/1000)) month, count(*) count from ms_article group by year,month; 还有可能出现null年null月 是因为数据库数据不规范 不是13位数
9.RequestMapping 路径写少了一个s,到时发送请求找不到控制器方法,页面提交失败,弹出错误信息是空白,后台查找到用户信息,但是无任何报错,其实就是没进去 获取用户信息的控制器方法。
10.登录功能,点击登录,login请求发送,得到token,但是页面不跳转,后台也没有报错,发现根本只发送了login请求,没有获取用户信息的请求(自然也没有进入相应的控制器方法),后端代码没发现错误。结果是返回格式Result出问题了,我更改了Result的属性将success->flag,但是前端没有修改,导致前端取不到想要的数据,获取用户信息的请求无法发出,页面也自然无法跳转
11.出现bug,文章详情 ,文章分类取不到数据,接口没问题,传出去的json数据也是有的,就是拿不到, 代码只改了方法名,然后就莫名其妙好了,可能前端规定了方法名?
12.显示评论列表,依然是后台有数据,前端没收到,原因toUser为null,仔细看就发现,其实查询toUser 和查询用户返回的格式都是UserVo,容易混淆,再排查一次发现set toUser 写成了Author ,导致toUser为空,前端拿不到数据,显示不出来
13.if (parent == 0 || parent == null) 和if (parent == null || parent == 0)
其实是不一样的,第一个会导致空指针 parent是long类型,默认值为0L。 这里不是很懂(留坑)
14.写文章上传图片的时候,图片会裂开,导致的原因可能是 ①huanan写错 地域名称
② url 前面加http:// 最后加/ ③ bucket 是自己空间的名称
Configuration cfg = new Configuration(Region.huanan());
public static final String url = "http://r2gl1mo31.hn-bkt.clouddn.com/";
String bucket = "blog-xiaoduzi";
15.不知道怎么回事 ,mp不会自动帮我赋值(就是mp分页插件,方法的参数会自动注入sql语句的parameterType),所以只能 加@Param注解
IPage<Article> listArticle(Page<Article> page,
@Param("categoryId") Long categoryId,
@Param("tagId") Long tagId,
@Param("year") String year,
@Param("month") String mon
th);
//这里解释一下@Param注解的作用,当我们的sql中需要多个参数的时候,Maybatis会将参数列表中的参数封装成一个Map进行传递,这个过程是通过@Param来实现的,@Param注解括号中的值会作为key,value就是参数实际的值。解析参数的时候会按照@Param中定义的key获取对应的值。如上图中所
以后修改包名,记得同时修改 pox.xml文件里面的groupId,在代码目录中找到.idea文件里面的workspace.xml 里面的configuration 里面的option valuve 将其修改为想要的报名 ,最后还有就是Config包下的mybatis配置类那里的mapperScan修改后的包名的mapper
17.
在加入了Cache统一缓存之后,写完文章返回首页,出现很大的延迟现象
对此,在发布文章的借口上面再加一个自定义的注解CacheClear在发布文章之前清除缓存
CacheClear自定义注解类
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface CacheClear {
}
CacheAspect 切面类
@Pointcut("@annotation(com.xiaoduzi.blog.common.cache.CacheClear)")
public void clear(){}
@Before("clear()")
public void clear (JoinPoint pjp){
for (String key : keys) {
redisTemplate.delete(key);
}
keys.clear();
System.out.println("清理了缓存————————————————————————————————————————————");
}
Aspect 定义一个切面,切面定义了切点和通知的关系 编写具体代码 执行需求
18.技术总结
JWT+Redis
@Autowired
private RedisTemplate<String,String> redisTemplate;
JWTUtils
public class JWTUtils {
//密钥
private static final String jwtToken = "123456xiaoduzi!@#$%";
public static String createToken(Long userId){
Map<String,Object> claims = new HashMap<>();
claims.put("userId",userId);
JwtBuilder jwtBuilder = Jwts.builder()
.signWith(SignatureAlgorithm.HS256, jwtToken) // 签发算法,秘钥为jwtToken
.setClaims(claims) // body数据,要唯一,自行设置
.setIssuedAt(new Date()) // 设置签发时间
.setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000));// 一天的有效时间
String token = jwtBuilder.compact();
return token;
}
public static Map<String, Object> checkToken(String token){
try {
Jwt parse = Jwts.parser().setSigningKey(jwtToken).parse(token);
return (Map<String, Object>) parse.getBody();
}catch (Exception e){
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
String token = JWTUtils.createToken(100L);
System.out.println(token);
Map<String, Object> map = JWTUtils.checkToken(token);
System.out.println(map.get("userId"));//获取userId
}
}
loginServiceImpl
login
logout 退出登录
register 注册
checkToken 在loginInterceptor登录拦截器用到
还有在aop中也用到redis
MD5加密
loginServiceImpl类
加密盐slat
private static final String slat = "xiaoduzi!@#$";
password = DigestUtils.md5Hex(password + slat);
BCryptPasswordEncode
String password= "qwe123"; String hashpassword = new BCryptPasswordEncoder().encode(password); boolean match = new BCryptPasswordEncoder().matches(password,hashpassword);
1)加密(encode):注册用户时,使用SHA-256+随机盐+密钥把用户输入的密码进行hash处理,得到密码的hash值,然后将其存入数据库中。
(2)密码匹配(matches):用户登录时,密码匹配阶段并没有进行密码解密(因为密码经过Hash处理,是不可逆的),而是使用相同的算法把用户输入的密码进行hash处理,得到密码的hash值,然后将其与从数据库中查询到的密码hash值进行比较。如果两者相同,说明用户输入的密码正确。
思考:既然这种加密方式是不可逆的,那么当用户忘记密码后呢?后台管理也查看不到密码。
实际上,现在有部分是通过某种加密方式,用加密包对密码进行加密,存储到数据库中,可以使用相应的解密包能获取到用户的原始密码。但现在为了用户的信息安全,实际上是做到连后台管理员都无法直接看到用户的密码信息。现在大多数系统都是绑定手机或者邮箱之类的,当用户忘记密码后都是通过手机验证码或者邮箱的形式让用户重置密码,并不能够直接找系统管理员获取到密码。
原文链接:简述BCryptPasswordEncode_琴瑟和鸣1的博客-CSDN博客
登录认证+ 权限认证
SecurityConfig配置
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
//获取 加密之后的密码
public static void main(String[] args) {
//加密策略 MD5不安全彩虹表可以破解 MD5 加盐
// encode里面就是加密之前的密码
String xiaoduzi = new BCryptPasswordEncoder().encode("qwe123");
//$2a$10$T5yBWwtmwmO62xzR2ZFqTuXh/Hebx3mObdyaJjDFduAIr6BbUZ0Si 利用hash不可逆
System.out.println(xiaoduzi);
}
@Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//开启登录认证
http.authorizeRequests() //开启登录认证
// .antMatchers("/user/findAll").hasRole("admin") //访问接口需要admin的角色
.antMatchers("/css/**").permitAll()
.antMatchers("/img/**").permitAll()
.antMatchers("/js/**").permitAll()
.antMatchers("/plugins/**").permitAll()
//自定义service 来去实现实时的权限认证
.antMatchers("/admin/**").access("@authService.auth(request,authentication)")
.antMatchers("/pages/**").authenticated()
.and().formLogin()
.loginPage("/login.html") //自定义的登录页面
.loginProcessingUrl("/login") //登录处理接口
.usernameParameter("username") //定义登录时的用户名的key 默认为username
.passwordParameter("password") //定义登录时的密码key,默认是password
.defaultSuccessUrl("/pages/main.html")
.failureUrl("/login.html")
.permitAll() //通过 不拦截,更加前面配的路径决定,这是指和登录表单相关的接口 都通过
.and().logout() //退出登录配置
.logoutUrl("/logout") //退出登录接口
.logoutSuccessUrl("/login.html")
.permitAll() //退出登录的接口放行
.and()
.httpBasic()
.and()
.csrf().disable() //csrf关闭 如果自定义登录 需要关闭
.headers().frameOptions().sameOrigin();
}
}
登录
权限
线程池的应用
将阅读量的update放入线程池里面,不影响主线程的性能
ThreadLocal的应用
①在登录拦截器中的应用
get:获取当前线程用户的信息
put: 将当前对象放入线程
remove:移除当前线程 关键* 在全部方法执行完之后若不执行remove可能会导致内存泄露
ThreadLocal内存泄漏问题
ThreadLocalMap中使用的key为ThreadLocal的弱引用,而value是强引用,如果ThreadLocal在没有外部强引用的情况下,在垃圾回收时,key就会被清理掉,而value不会被清理掉。这样一来,ThreadLocalMap中就会出现key为null的Entry。假如我们不做任何措施的话,value永远无法被GC回收,这个时候就可能会产生内存泄漏。ThreadLocalMap实现中已经考虑了这种情况,再调用remove方法的时候,会清理key为null的记录。所以使用完ThreadLocal方法后,最好手动调用remove方法。
在发布文章部分,使用get方法得到当前用户,需要为新发布的文章填充作者信息
我在login和register方法最后都将user put进了ThreadLocal