# 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函数:


#### OSI模型

###### 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多线程相关
## 线程状态转换

- 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;
}
```
>添加虚拟路径映射到本地磁盘文件,用户回显文件。