# lumos_news **Repository Path**: lumos1/lumos_news ## Basic Information - **Project Name**: lumos_news - **Description**: 曲靖师范学院信息工程学院 2017112117余远东,毕业设计!请勿他用 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: http://yuyuandong.top - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-11-27 - **Last Updated**: 2022-08-05 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # lumos_news(智能新闻推送系统,编程日志) #### 介绍 曲靖师范学院信息工程学院 2017112117余远东,毕业设计!请勿他用 #### 软件架构 软件架构说明 基于springboot+mybatis-plus+redis+shiro+layui+git+maven 使用webmagic进行新闻数据的抓取 spring-mail, 使用spring定时任务,自动抓取数据。更新数据 shiro 整合 redis = Dec 19, 2020 11:11 AM 1. 添加redis,springboot依赖 ```xml org.springframework.boot spring-boot-starter-data-redis ``` 2. 配置redis ```yaml #配置redis redis: host: 47.100.45.42 port: 6379 database: 3 password: yyd704551 ``` 3. 编写自定义cache缓存处理器 ```java /** * @Description:自定义redis cache缓存处理器 * @Author: yuyd * @Email: yuyd@asiainfo.com * @Date: 2020/12/19 9:39 * @Version 1.0 */ public class RedisCache implements Cache { @Autowired RedisTemplate redisTemplate; @Override public V get(K k) throws CacheException { V v = (V)redisTemplate.opsForValue().get(k.toString()); return v; } @Override public V put(K k, V v) throws CacheException { //设置序列化规则 redisTemplate.setDefaultSerializer(new StringRedisSerializer()); redisTemplate.opsForValue().set(k.toString(),v); return null; } @Override public V remove(K k) throws CacheException { return null; } @Override public void clear() throws CacheException { } @Override public int size() { return 0; } @Override public Set keys() { return null; } @Override public Collection values() { return null; } } ``` 4. 配置缓存管理器 ```java package com.wisnews.config.shiro; import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheException; import org.apache.shiro.cache.CacheManager; import org.springframework.beans.factory.annotation.Autowired; /** * @Description:redis shiro 缓存管理器 * @Author: yuyd * @Email: yuyd@asiainfo.com * @Date: 2020/12/19 9:23 * @Version 1.0 */ public class RedisCacheManager implements CacheManager { @Autowired RedisCache redisCache; @Override public Cache getCache(String s) throws CacheException { return redisCache; } } ``` 5. 修改shiro配置 ```java //关联缓存管理器 userRealm.setCacheManager(redisCacheManager()); //开启全局缓存 userRealm.setCachingEnabled(true); //认证缓存 userRealm.setAuthenticationCachingEnabled(true); userRealm.setAuthenticationCacheName("authenticationCache"); //授权缓存 userRealm.setAuthorizationCachingEnabled(true); userRealm.setAuthorizationCacheName("authorizationCache"); ``` 6.解决bug ```java package com.wisnews.config.shiro; import org.apache.shiro.util.SimpleByteSource; import java.io.Serializable; /** * @Description:这个类解决shiro 序列化出错问题 Caused by: java.io.NotSerializableException: org.apache.shiro.util.SimpleByteSource * @Author: yuyd * @Email: yuyd@asiainfo.com * @Date: 2020/12/19 10:44 * @Version 1.0 */ public class MyByteSource extends SimpleByteSource implements Serializable { public MyByteSource(String string) { super(string); } } //修改盐匹配器 return new SimpleAuthenticationInfo(operator, operator.getPassword(),new MyByteSource(operator.getSalt()),getName()); ``` 使用shiro加盐加密 = Dec 19, 2020 11:34 AM - 编写加密工具类 ```java package com.wisnews.util; import org.apache.shiro.crypto.hash.SimpleHash; import java.util.UUID; /** * @Description:shiro 加密工具类 * @Author: yuyd * @Email: yuyd@asiainfo.com * @Date: 2020/12/15 10:47 * @Version 1.0 */ public class ShiroUtil { /** * 生成 16位 的的随机盐值 */ public static String createSalt(){ String str = UUID.randomUUID().toString().replaceAll("-", ""); return str.substring(0,16); } /** * 加盐加密 * @param src 原始密码 * @param salt 盐值 */ public static String encryption(Object src, String salt){ return new SimpleHash("MD5", src, salt, Constants.SYS_CODE.PSWD_DEGREE).toString(); } } ``` - realm中配置 ```java /* //创建凭证匹配器 HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); //设置加密算法名称 credentialsMatcher.setHashAlgorithmName("MD5"); //设置hash迭代次数 credentialsMatcher.setHashIterations(Constants.SYS_CODE.PSWD_DEGREE); //覆盖默认凭证匹配器 userRealm.setCredentialsMatcher(credentialsMatcher); ``` - service中调用加密工具类 ```java //生成盐 String salt = ShiroUtil.createSalt(); //保存盐 entity.setSalt(salt); //密码加密 entity.setPassword(ShiroUtil.encryption(entity.getPassword(),salt)); ``` - 左侧菜单,生成树算法算法实现 */ ```java @RequestMapping("test1") public class test1 { public Result testGenMenu(){ List list = permissionService.list(); //根节点 MenuTreeNode root = new MenuTreeNode(); root.setData(null); HashMap permissionHashMap = new HashMap<>(); list.forEach(permission -> { permissionHashMap.put(permission.getPermissionId(),permission); if (permission.getPid().equals("0")){ //父节点 MenuTreeNode menuTreeNode = new MenuTreeNode(); menuTreeNode.setData(permission); root.getChildren().add(menuTreeNode); } }); list.forEach(permission -> { root.getChildren().forEach( permissionMenuTreeNode -> { if (permission.getPid().equals(permissionMenuTreeNode.getData().getPermissionId())){ MenuTreeNode menuTreeNode = new MenuTreeNode(); menuTreeNode.setData(permission); menuTreeNode.setChildren(null); permissionMenuTreeNode.getChildren().add(menuTreeNode); } } ); } ); return new Result<>(Constants.SYS_CODE.SUCCESS, Constants.SYS_CODE.SUCCESS_MESSAGE,root); }} ``` Dec 19, 2020 6:23 PM - shiro整合redis如果直接extends SimpleByteSource 反序列化会出现没有无参构造,反序列化错误。所以只能自己模仿SimpleByteSource 实现implements ByteSource, Serializable ```java package com.wisnews.config.shiro; import org.apache.shiro.codec.Base64; import org.apache.shiro.codec.CodecSupport; import org.apache.shiro.codec.Hex; import org.apache.shiro.util.ByteSource; import java.io.File; import java.io.InputStream; import java.io.Serializable; import java.util.Arrays; /** * @Description:这个类解决shiro 序列化出错问题 Caused by: java.io.NotSerializableException: org.apache.shiro.util.SimpleByteSource * @Author: yuyd * @Email: yuyd@asiainfo.com * @Date: 2020/12/19 10:44 * @Version 1.0 */ public class MyByteSource implements ByteSource, Serializable { /*这里将final去掉了,去掉后要在后面用getter和setter赋、取值*/ private byte[] bytes; private String cachedHex; private String cachedBase64; /*添加了一个无参构造方法*/ public MyByteSource(){} public MyByteSource(byte[] bytes) { this.bytes = bytes; } public MyByteSource(char[] chars) { this.bytes = CodecSupport.toBytes(chars); } public MyByteSource(String string) { this.bytes = CodecSupport.toBytes(string); } public MyByteSource(ByteSource source) { this.bytes = source.getBytes(); } public MyByteSource(File file) { this.bytes = (new MyByteSource.BytesHelper()).getBytes(file); } public MyByteSource(InputStream stream) { this.bytes = (new MyByteSource.BytesHelper()).getBytes(stream); } public static boolean isCompatible(Object o) { return o instanceof byte[] || o instanceof char[] || o instanceof String || o instanceof ByteSource || o instanceof File || o instanceof InputStream; } /*这里加了getter和setter*/ public void setBytes(byte[] bytes) { this.bytes = bytes; } public byte[] getBytes() { return this.bytes; } public boolean isEmpty() { return this.bytes == null || this.bytes.length == 0; } public String toHex() { if (this.cachedHex == null) { this.cachedHex = Hex.encodeToString(this.getBytes()); } return this.cachedHex; } public String toBase64() { if (this.cachedBase64 == null) { this.cachedBase64 = Base64.encodeToString(this.getBytes()); } return this.cachedBase64; } public String toString() { return this.toBase64(); } public int hashCode() { return this.bytes != null && this.bytes.length != 0 ? Arrays.hashCode(this.bytes) : 0; } public boolean equals(Object o) { if (o == this) { return true; } else if (o instanceof ByteSource) { ByteSource bs = (ByteSource)o; return Arrays.equals(this.getBytes(), bs.getBytes()); } else { return false; } } private static final class BytesHelper extends CodecSupport { private BytesHelper() { } public byte[] getBytes(File file) { return this.toBytes(file); } public byte[] getBytes(InputStream stream) { return this.toBytes(stream); } } /*取代原先加盐的工具类*/ public static class Util{ public static ByteSource bytes(byte[] bytes){ return new MyByteSource(bytes); } public static ByteSource bytes(String arg0){ return new MyByteSource(arg0); } } } ``` Dec 21, 2020 9:31 AM ## shiro整合redis 注意点: - 如果我们在realm中把认证通过的信息存入realm中,```session```.setAttribute(Constants.SYS_CODE.LOGIN_USER, operator);// 放入session 当登录过一次后再次登录就先走redis,不经过自定义realm。就无法取到session中的认证用户信息。 ``` // 认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { log.error("执行了=>认证 doGetAuthenticationInfo"); UsernamePasswordToken usertoken = (UsernamePasswordToken) token; //根据用户名查询 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper().eq(Operator::getCode,usertoken.getUsername()); Operator operator = operatorService.getOne(queryWrapper); if (null == operator) { //根据邮箱查询 LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper().eq(Operator::getEmail,usertoken.getUsername()); operator = operatorService.getOne(lambdaQueryWrapper); if (null == operator){ return null;// 抛出异常 UnknownAccountException } } Subject currentUser = SecurityUtils.getSubject(); Session session = currentUser.getSession(); session.setAttribute(Constants.SYS_CODE.LOGIN_USER, operator);// 放入session // 密码认证,shiro做了 return new SimpleAuthenticationInfo(operator, operator.getPassword(),new MyByteSource(operator.getSalt()),getName());// 第一个参数传递用户的信息,就可以通过获取当前用户的getPrincipal获得 } ``` - 数据库级别对菜单进行排序 ```java @Select("SELECT a.* FROM t_sec_permission a LEFT JOIN t_sec_role_permission b ON" + " a.permission_id = b.permission_id WHERE b.role_id = #{operatorRole} ORDER BY a.type,a.pid,sort_id ") List getPermissionForRole(String operatorRole); ``` - order by key1,key2,key3 按key1,key2,key2 关键字进行排序 #前端框架相关问题综述 ### js将数据转化为json应注意的问题(2020/12/22) ```javascript $.parseJSON(str); //将字符串转化为json对象 JSON.stringify(params); //将dom对象转化为json ``` ### 父页面向子页面传递数据 ```javascript parent.$('#oneRowTableData').val(); //选择父页面的dom对象, ``` #shiro中的权限过滤器 ```java /*anon(AnonymousFilter.class), authc(FormAuthenticationFilter.class), authcBasic(BasicHttpAuthenticationFilter.class), authcBearer(BearerHttpAuthenticationFilter.class), logout(LogoutFilter.class), noSessionCreation(NoSessionCreationFilter.class), perms(PermissionsAuthorizationFilter.class), port(PortFilter.class), rest(HttpMethodPermissionFilter.class), roles(RolesAuthorizationFilter.class), ssl(SslFilter.class), user(UserFilter.class), invalidRequest(InvalidRequestFilter.class); */ ``` #spring boot 定时任务 -使用 ```java @EnableScheduling //在application使用 开启定时任务 //在方法上使用 @Scheduled(cron = "")//使用表达式在特定时间执行特定方法 ``` -cron表达式举例 ``` 字段 允许值 允许的特殊字符 秒 0-59 , - * / 分 0-59 , - * / 小时 0-23 , - * / 日期 1-31 , - * ? / L W C 月份 1-12 或者 JAN-DEC , - * / 星期 1-7 或者 SUN-SAT , - * ? / L C # 年(可为空) 留空, 1970-2099 , - * / 表达式举例 "0 0 12 * * ?" 每天中午12点触发 "0 15 10 ? * *" 每天上午10:15触发 "0 15 10 * * ?" 每天上午10:15触发 "0 15 10 * * ? *" 每天上午10:15触发 "0 15 10 * * ? 2005" 2005年的每天上午10:15触发 "0 * 14 * * ?" 在每天下午2点到下午2:59期间的每1分钟触发 "0 0/5 14 * * ?" 在每天下午2点到下午2:55期间的每5分钟触发 "0 0/5 14,18 * * ?" 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发 "0 0-5 14 * * ?" 在每天下午2点到下午2:05期间的每1分钟触发 "0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44触发 "0 15 10 ? * MON-FRI" 周一至周五的上午10:15触发 "0 15 10 15 * ?" 每月15日上午10:15触发 "0 15 10 L * ?" 每月最后一日的上午10:15触发 "0 15 10 ? * 6L" 每月的最后一个星期五上午10:15触发 "0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最后一个星期五上午10:15触发 "0 15 10 ? * 6#3" 每月的第三个星期五上午10:15触发 ``` #WebMagic爬虫框架结合springboot,mybatis将爬虫数据存入数据库 * 导入相关依赖 ```xml us.codecraft webmagic-core 0.7.3 us.codecraft webmagic-extension 0.7.3 org.slf4j slf4j-log4j12 ``` * 编写核心爬虫类 ```java /** * @Description:网易热点新闻,webmagic数据爬虫工具类 * @Author: yuyd * @Email: yuyd@asiainfo.com * @Date: 2020/12/25 10:26 * @Version 1.0 */ @Component @Slf4j public class WebMagic implements PageProcessor { private Site site = Site.me().setRetryTimes(3).setSleepTime(100).setCharset("utf-8"); @Override public void process(Page page) { //主页面中的二级链接 使用正则表达式 List alllink = page.getHtml().links().regex("(https://\\w+\\.163\\.com/20/\\w+/\\w+/\\w+\\.html)").all(); page.addTargetRequests(alllink); log.info("爬虫开始,获取链接总数"+alllink.size()); Selectable url = page.getUrl(); if (url.toString().contains("ent") || StringUtils.isBlank(page.getHtml().$(".post_content_main h1","text").toString())){ //跳过娱乐新闻,娱乐新闻乱码 page.setSkip(true); log.info(" << 娱乐新闻 >> 或 没有获取到<<新闻标题>> 跳过页面"+url.toString()); } //存储值 添加过滤规则 page.putField("newsTitle",page.getHtml().$(".post_content_main h1","text")); page.putField("newsAnnounceTime",page.getHtml().$(".post_content_main .post_time_source","text")); page.putField("newsMedia",page.getHtml().$("#ne_article_source","text")); page.putField("newsContent",page.getHtml().$("#endText p").all()); page.putField("newsType", page.getHtml().$(".clearfix .post_crumb a","text").all()); page.putField("newsFromUrl",url.toString()); } @Override public Site getSite() { return site; } } ``` * 创建爬虫工具类 ```java /** * @Description:创建爬虫 * @Author: yuyd * @Email: yuyd@asiainfo.com * @Date: 2020/12/26 14:17 * @Version 1.0 */ @Service public class CrawlDataAutoSchedService { @Autowired private NewsFor163Pipeline newsFor163Pipeline; @Autowired WebMagic webMagic; /* * 开启定时任务自动调度 * 也可以手动调度 * */ @Scheduled(cron = "0 0 23 * * ?") public void crawlData(){ Spider.create(webMagic) .addUrl("http://news.163.com/special/0001386F/rank_whole.html") .addPipeline(newsFor163Pipeline) .thread(5) .run(); } public void crawlData(WebMagic web, String url, Pipeline pipeline){ Spider.create(web) .addUrl(url) .addPipeline(pipeline) .thread(5) .run(); } public void crawlData(WebMagic web, String url, Pipeline pipeline,Integer thread){ Spider.create(web) .addUrl(url) .addPipeline(pipeline) .thread(thread) .run(); } } ``` * 最重要的写入数据库 >webmagic 如果使用默认的 Pipeline 是使用的控制台打印 源码如下 ```java package us.codecraft.webmagic.pipeline; import us.codecraft.webmagic.ResultItems; import us.codecraft.webmagic.Task; import java.util.Map; /** * Write results in console.
* Usually used in test. * * @author code4crafter@gmail.com
* @since 0.1.0 */ public class ConsolePipeline implements Pipeline { @Override public void process(ResultItems resultItems, Task task) { System.out.println("get page: " + resultItems.getRequest().getUrl()); for (Map.Entry entry : resultItems.getAll().entrySet()) { System.out.println(entry.getKey() + ":\t" + entry.getValue()); } } } ``` >我们只需要实现Pipeline 接口 编写自己的Pipeline即可实现存入数据库,或其他方式 > 还可以使用注解方式,后期学习后更新 > webmagic官方中文文档位置 http://webmagic.io/docs/zh/ ```java /** *@Description:自定义Pipeline 把爬虫数据写入数据库 *@Author: yuyd *@Email: yuyd@asiainfo.com *@Date: 2020/12/25 16:07 *@Version 1.0 */ @Component @Slf4j public class NewsFor163Pipeline implements Pipeline { @Autowired private NewsServiceImpl newsService; //resultItems 中存储了,我们爬取的map @Override public void process(ResultItems resultItems, Task task) { StringBuffer stringBuffer = new StringBuffer(""); log.info("==》将爬虫信息写入数据库!"); Map all = resultItems.getAll(); News news = new News(); List newsContent = (List) all.get("newsContent"); newsContent.forEach(onenews ->{ stringBuffer.append(onenews); }); news.setNewsContent(stringBuffer.toString()); news.setNewsTitle(all.get("newsTitle").toString()); news.setNewsType(all.get("newsType").toString()); //news.setNewsMedia(all.get("media").toString()); String newsAnnounceTime = all.get("newsAnnounceTime").toString().substring(1, 20); news.setNewsAnnounceTime(newsAnnounceTime); news.setNewsMedia(all.get("newsMedia").toString()); news.setNewsFromUrl(all.get("newsFromUrl").toString()); newsService.save(news); log.info("《==写入数据库完成"); } } ``` # mysql相关 ### 字符串,截取相关函数,相当强大(通过查询数据库就把字符串处理好,可以提高速度,java处理速度没有mysql快) ```sql MySQL 字符串截取函数:left(), right(), substring(), substring_index()。还有 mid(), substr()。其中,mid(), substr() 等价于 substring() 函数,substring() 的功能非常强大和灵活。 1. 字符串截取:left(str, length) mysql> select left('example.com', 3); +-------------------------+ | left('example.com', 3) | +-------------------------+ | exa | +-------------------------+ 2. 字符串截取:right(str, length) mysql> select right('example.com', 3); +--------------------------+ | right('example.com', 3) | +--------------------------+ | com | +--------------------------+ 实例: #查询某个字段后两位字符 select right(last3, 2) as last2 from historydata limit 10; #从应该字段取后两位字符更新到另外一个字段 update `historydata` set `last2`=right(last3, 2); 3. 字符串截取:substring(str, pos); substring(str, pos, len) 3.1 从字符串的第 4 个字符位置开始取,直到结束。 mysql> select substring('example.com', 4); +------------------------------+ | substring('example.com', 4) | +------------------------------+ | mple.com | +------------------------------+ 3.2 从字符串的第 4 个字符位置开始取,只取 2 个字符。 mysql> select substring('example.com', 4, 2); +---------------------------------+ | substring('example.com', 4, 2) | +---------------------------------+ | mp | +---------------------------------+ 3.3 从字符串的第 4 个字符位置(倒数)开始取,直到结束。 mysql> select substring('example.com', -4); +-------------------------------+ | substring('example.com', -4) | +-------------------------------+ | .com | +-------------------------------+ 3.4 从字符串的第 4 个字符位置(倒数)开始取,只取 2 个字符。 mysql> select substring('example.com', -4, 2); +----------------------------------+ | substring('example.com', -4, 2) | +----------------------------------+ | .c | +----------------------------------+ 我们注意到在函数 substring(str,pos, len)中, pos 可以是负值,但 len 不能取负值。 4. 字符串截取:substring_index(str,delim,count) 4.1 截取第二个 '.' 之前的所有字符。 mysql> select substring_index('www.example.com', '.', 2); +------------------------------------------------+ | substring_index('www.example.com', '.', 2) | +------------------------------------------------+ | www.example | +------------------------------------------------+ 4.2 截取第二个 '.' (倒数)之后的所有字符。 mysql> select substring_index('www.example.com', '.', -2); +-------------------------------------------------+ | substring_index('www.example.com', '.', -2) | +-------------------------------------------------+ | example.com | +-------------------------------------------------+ 4.3 如果在字符串中找不到 delim 参数指定的值,就返回整个字符串 mysql> select substring_index('www.example.com', '.coc', 1); +---------------------------------------------------+ | substring_index('www.example.com', '.coc', 1) | +---------------------------------------------------+ | www.example.com | +---------------------------------------------------+ ``` ### 因为爬虫存入数据库的时间是字符串,所以在查询后用转化函数进行处理 # JS相关 ### js获取url参数,函数 ```javascript var getQueryString = function (name) { let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i"); let r = window.location.search.substr(1).match(reg); if (r != null) { return decodeURIComponent(r[2]); }; return null; }; ``` # 网络相关(2021/1/13) #### 图解socket函数: ![avatar](https://upload-images.jianshu.io/upload_images/11362584-31d594363f8cafa0.png?imageMogr2/auto-orient/strip|imageView2/2/w/554/format/webp) ![avatar](https://upload-images.jianshu.io/upload_images/11362584-ec76f69e2ea76d41.jpeg?imageMogr2/auto-orient/strip|imageView2/2/w/478/format/webp) #### OSI模型 ![avatar](https://upload-images.jianshu.io/upload_images/11362584-ec76f69e2ea76d41.jpeg?imageMogr2/auto-orient/strip|imageView2/2/w/478/format/webp) ###### TCP/IP协议参考模型把所有的TCP/IP系列协议归类到四个抽象层中 - 应用层:TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet 等等 - 传输层:TCP,UDP - 网络层:IP,ICMP,OSPF,EIGRP,IGMP - 数据链路层:SLIP,CSLIP,PPP,MTU # springBoot 参数转化问题 ### 当前台时间格式无法转化为后台Date或LocalDateTime 时在对象属性上加 ```java @JsonFormat(locale="zh", timezone="GMT+8", pattern="yyyy-MM-dd HH:mm:ss"); ``` # java多线程相关 ## 线程状态转换 ![avatar](https://img-blog.csdn.net/20150309140927553) - 1、新建状态(New):新创建了一个线程对象。 - 2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。 - 3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。 - 4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种: - (一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁) - (二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。 - (三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁) - 5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。 ## springBoot 相关 ```java @ASYN //会导致局部变量丢失 //通过邮箱发送验证码 //这里前台不返回邮箱验证码,保证安全,使用邮箱作为key,验证码作为value 存入redis @RequestMapping(value = "sendAuthCode/{email}",method = RequestMethod.POST) public Result sendAuthCodeByEmail(@PathVariable("email") String email){ if (StringUtils.isBlank(email)){ return new Result(Constants.SYS_CODE.FAIL, Constants.SYS_CODE.SUCCESS_MESSAGE); } String authCode = AuthCodeUtil.genRodomCode(); String auth = (String)redisTemplate.opsForValue().get(email); //缓存中 不存在验证码 if (StringUtils.isBlank(auth)){ try { qqEmailService.sendSimpleMail(email,"智慧新闻验证码","您正在智慧新闻验证邮箱,您的验证码为 "+ authCode+" 三分钟后超时!请勿将验证码告诉他人!"); // 发送邮件为异步方法,authCode 没有值 redisTemplate.opsForValue().set(email,authCode,3, TimeUnit.MINUTES); }catch (MailException mailException){ log.error("验证码发送失败"+mailException); return new Result(Constants.SYS_CODE.FAIL,"验证码发送失败!"); } }else { //验证码重复发送 return new Result(Constants.SYS_CODE.FAIL,"重复发送,三分钟后重试!"); } //验证码存入redis 3分钟后超时 redisTemplate.opsForValue().set(email,authCode,3, TimeUnit.MINUTES); return new Result(Constants.SYS_CODE.SUCCESS, Constants.SYS_CODE.SUCCESS_MESSAGE); } ``` # shiro 多realm认证 #### 多Realm登录认证策略有三种: - 1)FirstSuccessfulStrategy:第一个Realm验证成功就登录成功 - 2)AtLeastOneSuccessfulStrategy:至少有一个Realm验证成功就登录成功(默认策略) - 3)AllSuccessfulStrategy,全部的Realm验证必须都成功,则登录成功 # spring-aop ```java package com.wisnews.config.aspectjLog; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.wisnews.pojo.sys.entity.VisitLog; import com.wisnews.service.sys.IVisitLogService; import com.wisnews.util.DateUtil; import eu.bitwalker.useragentutils.UserAgent; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Objects; /** * @Description:aop切面日志 * @Author: yuyd * @Email: yuyd@asiainfo.com * @Date: 2021/2/8 10:03 * @Version 1.0 */ @Component @Slf4j @Aspect //标注这是一个aop 切面 public class AopVisitLog { @Autowired private IVisitLogService iVisitLogService; //定义切入点 //监控所有web请求 @Pointcut("execution(public * com.wisnews.controller..*.*(..))") public void controllerPointcut() { } /** * 环绕通知 * * @param point 切入点 * @return 原方法返回值 * @throws Throwable 异常信息 */ @Around("controllerPointcut()") public Object aopMonitoring(ProceedingJoinPoint point) throws Throwable { log.info("======>进入日志切面类"); //获取请求对象 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = Objects.requireNonNull(attributes).getRequest(); //获取请求信息 long startTime = System.currentTimeMillis(); Object result = point.proceed(); String header = request.getHeader("User-Agent"); UserAgent userAgent = UserAgent.parseUserAgentString(header); String visitControllerMethod = String.format("%s.%s", point.getSignature().getDeclaringTypeName(), point.getSignature().getName()); VisitLog visitLog = new VisitLog() .setThreadId(Long.toString(Thread.currentThread().getId())) .setIpAddr(getIpAddr(request)) .setUrl(request.getRequestURL().toString()) .setVisitControllerMethod(visitControllerMethod) .setHttpMethod(request.getMethod()) .setTimeCost(Long.toString(System.currentTimeMillis() - startTime)) .setUseragent(userAgent.toString()) .setBrowser(userAgent.getBrowser().getName()) .setOs(userAgent.getOperatingSystem().getName()) .setCreateTime(DateUtil.getLocalDateTime()); iVisitLogService.save(visitLog); return result; } /** * 获取访问ip地址 */ public static String getIpAddr(HttpServletRequest request) { String ip = request.getHeader("x-forwarded-for"); if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } String comma = ","; String localhost = "127.0.0.1"; if (ip.contains(comma)) { ip = ip.split(",")[0]; } if (localhost.equals(ip)) { // 获取本机真正的ip地址 try { ip = InetAddress.getLocalHost().getHostAddress(); } catch (UnknownHostException e) { log.error(e.getMessage(), e); } } return ip; } } ``` # spring boot 文件上传,虚拟路径映射 - 配置文件中添加文件上传路径,方便后期修改 ```yaml #自定义参数 custom: parameter: #文件上传路径 file-upload-path: E:/path ``` - mvc配置虚拟路径映射 ```java @Configuration public class MyMvcConfig implements WebMvcConfigurer { @Autowired private IViewControllerService viewControllerService; @Value("${custom.parameter.file-upload-path}") private String upPath; //配置视图映射 @Override public void addViewControllers(ViewControllerRegistry registry) { List viewControllers = viewControllerService.list(); viewControllers.forEach(viewController -> { registry.addViewController( viewController.getUrlPath() ) .setViewName(viewController.getViewName()); }); } //配置虚拟路径映射 @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/newsImg/**").addResourceLocations("file:/"+upPath+"/"); WebMvcConfigurer.super.addResourceHandlers(registry); } } ``` - 上传文件接口 ```java /** * @Description:上传文件接口 * @Author: yuyd * @Email: yuyd@asiainfo.com * @Date: 2021/2/25 9:29 * @Version 1.0 */ @RestController @RequestMapping("/fileUpload") @Slf4j public class FileUploadController { @Value("${custom.parameter.file-upload-path}") private String upPath; @PostMapping("image") public FileUploadFinishVO uploadImage(@RequestParam("file") MultipartFile imgFile){ log.warn("上传文件开始"); FileUploadFinishVO fileUploadFinishVO = new FileUploadFinishVO(); if (imgFile.isEmpty()){ fileUploadFinishVO.setCode(-9999); return fileUploadFinishVO; } String originalFilename = imgFile.getOriginalFilename(); int lastIndex = originalFilename.lastIndexOf("."); String type = originalFilename.substring(lastIndex); String newFileName = generateRandomFileName()+type; try { imgFile.transferTo(new File(upPath,newFileName)); }catch (IOException e){ log.error(e.toString()); return fileUploadFinishVO; } log.warn(originalFilename+"文件上传完成!"); fileUploadFinishVO.setCode(0); //返回映射的虚拟路径 fileUploadFinishVO.setData(new Data().setSrc("/newsImg/"+newFileName).setTitle(newFileName)); return fileUploadFinishVO; }; //根据上传时间生成文件名 private String generateRandomFileName(){ String prefix = DateUtil.format(new Date(), "yyyyMMddHHmmss"); long timeInMillis = Calendar.getInstance().getTimeInMillis(); return prefix+"_"+timeInMillis; } } @lombok.Data @Accessors(chain = true) public class Data { private String src; //图片路径 private String title; } /** * @Description:文件上传完成返回VO * @Author: yuyd * @Email: yuyd@asiainfo.com * @Date: 2021/2/25 10:18 * @Version 1.0 */ @Data public class FileUploadFinishVO { private Integer code;//0表示成功,其它失败 private String msg;//提示信息 //一般上传失败后返回 private com.wisnews.pojo.sr.vo.Data data; } ``` >添加虚拟路径映射到本地磁盘文件,用户回显文件。