跨域处理 产生原因 跨域产生的原因是由于前端地址与后台接口不是同源，从而导致 ajax 不能发送 非同源产生的问题 Cookie、LocalStorage 和 IndexDB 无法获取 DOM 无法获得 AJAX 请求不能发送 同源条件 协议，端口，主机 三者相同即为同源 反之，其中只要 某一个 不一样则为不同源 解决方式 本地开发跨域 本地开发一般使用下面 3 种方式进行处理 vue前端的proxy 进行代理 后台开启 cors 使用 nginx 转发请求 生产环境跨域 生产环境一般使用下面 2 种方式进行处理 后台开启 cors (以java为例) @Configuration @RequiredArgsConstructor public class CorsConfig implements WebMvcConfigurer { /** * 开启跨域 */ @Override public void addCorsMappings(CorsRegistry registry) { // 设置允许跨域的路由 registry.addMapping("/**") // 设置允许跨域请求的域名 //.allowedOriginPatterns("*") .allowedOrigins("*") // 是否允许证书（cookies） .allowCredentials(true) // 设置允许的方法 .allowedMethods("*") // 跨域允许时间 .maxAge(3600); } } 使用 nginx 转发请求 例如：/api 代理至本机的1170端口 location /api { proxy_pass http://127.0.0.1:1170/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header REMOTE-HOST $remote_addr; add_header X-Cache $upstream_cache_status; #Set Nginx Cache add_header Cache-Control no-cache; expires 12h; }
记录下我的第一个python脚本 全民K歌签到脚本 import requests import json openkey = '' # 必填，不知道openkey哪里来的，抓包的时候/log上会有openkey，复制过来就好了 cookie = '' # Cookie key = '' # server酱key，不填不通知 headers = { 'Cookie': cookie, 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 QQJSSDK/1.0.0 Hippy/1.0 qua/V1_IPH_KG_6.21.8_449_APP_A qmkege/6.21.8 GDTMobSDK/4.10.36', 'Content-Type': 'application/json', 'Referer': 'https://kg.qq.com/vMission/index.html?hippy=vMission', 'Host': 'node.kg.qq.com', 'No-Chunked': 'true' } def get_uid_from_cookie(): kvs = cookie.split("; ") uid = '' for i in kvs: if i.find('uid=') >= 0: uid = i.split('=')[1] return uid def _sign_in_(): try: uid = get_uid_from_cookie() params = '{\"g_tk_openkey\":' + openkey + ',\"t_uid\":\"' + uid + '\",\"t_show_entry\":0,\"t_mapExtInfo\":{\"device_id\":\"\"},\"t_vctAppId\":[],\"ns\":\"KG_TASK\",\"cmd\":\"task.revisionSignInGetAward\",\"ns_inbuf\":\"\",\"mapExt\":{\"file\":\"taskJce\",\"cmdName\":\"GetSignInAwardReq\",\"l5api\":{\"modid\":503937,\"cmd\":589824},\"l5api_exp1\":{\"modid\":817089,\"cmd\":3801088}}}' sign_url = 'https://node.kg.qq.com/webapp/proxy?format=json&outCharset=utf-8&g_tk=' + openkey + '&g_tk_openkey=' + openkey msg = ''; resp = requests.post(sign_url, data=params, headers=headers) # print(resp.text) awards_jo = json.loads(resp.text)['data']['task.revisionSignInGetAward'] # awards_jo = json.loads( # '{"total":1,"awards":[{"Id":22,"num":3,"desc":"","option":0,"confValue":"","mapExtInfo":{}},{"Id":22,"num":3,"desc":"","option":0,"confValue":"","mapExtInfo":{}}],"ret":0,"msg":"","lotteryStatus":0}') if awards_jo['total'] > 0: num = 0 for award in awards_jo['awards']: num += award['num'] msg = "获得鲜花" + str(num) + "个" else: msg = awards_jo['msg'] except Exception: msg = 'cookie失效' return msg def send_message(content): content = requests.get('https://sc.ftqq.com/' + key + '.send?text=全民K歌签到结果&desp=' + content).text if __name__ == '__main__': msg = _sign_in_() print('msg: ', msg) if len(key) > 0: send_message(msg) 效果
之前简单介绍过Java自带的四种线程池的工厂方法。飞机 《阿里巴巴 Java开发手册》1.6并发处理 第二条 【强制】创建线程或线程池时请指定有意义的线程名称，方便出错时回溯。 第三条 【强制】线程资源必须通过线程池提供，不允许在应用中自行显式创建线程。 说明：使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销，解决 资源不足的问题。如果不使用线程池，有可能造成系统创建大量同类线程而导致消耗完内存或 者“过度切换”的问题。 第四条 【强制】线程池不允许使用 Executors 去创建，而是通过 ThreadPoolExecutor 的方式，这样 的处理方式让写的同学更加明确线程池的运行规则，规避资源耗尽的风险。 说明：Executors 返回的线程池对象的弊端如下： 1). FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE，可能会堆积大量的请求，从而导致 OOM。 2). CachedThreadPool 和 ScheduledThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE，可能会创建大量的线程，从而导致 OOM。 线程池配置 根据不同的业务配置不同的线程池, 对不通业务线程池命名，方便出错时回溯。 /** * @author yiidii Wang * @desc 线程池配置 */ @Configuration @EnableAsync public class ExecutorConfig { /** * 采集和分析线程的定时线程池（一般就两个线程） * @return */ @Bean("collectionScheduleExcutor") public Executor collectionScheduleExcutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setThreadNamePrefix("collectionScheduleExcutor-"); executor.setMaxPoolSize(5); executor.setCorePoolSize(2); executor.setQueueCapacity(0); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); return executor; } /** * CollectProxy线程的线程池 * @return */ @Bean("timerCollectExecutor") public Executor timerCollectExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setThreadNamePrefix("timerCollectExecutor"); executor.setMaxPoolSize(20); executor.setCorePoolSize(15); executor.setQueueCapacity(1000); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); return executor; } } 线程池的拒绝策略 线程池拒绝策略参考文章 Java线程池的拒绝策略 通过实现 RejectedExecutionHandler 接口扩展 /** * @author yiidii Wang * @desc 采集器线程池拒绝策略 */ private static final class MyTimerCollectionRejectedExecutionHandler implements RejectedExecutionHandler { public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { // 启用新的线程 try { final Thread t = new Thread(r, "Temporary task executor"); t.start(); } catch (Throwable e) { throw new RejectedExecutionException( "Failed to start a new thread", e); } } } 使用 启动新增注解 @SpringBootApplication @EnableScheduling public class PigeonApplication { public static void main(String[] args) { SpringApplication.run(PigeonApplication.class, args); } } 方法异步执行 /** * 每一秒执行一次 */ @Async("collectionScheduleExcutor") @Scheduled(cron = "0/1 * * * * ?") public void startTimerCollect() {} 如果需要注入线程池 /** * 采集的线程池 */ @Autowired @Resource(name = "timerCollectExecutor") private ThreadPoolTaskExecutor timerCollectExecutor; 效果
概述 大多时候，我们获取对象的方法都是直接new一个。但是，对于大对象的构造，或者构造耗时比较久的对象，比如数据库的连接对象、redis的连接对象、Http连接请求对象等等，我们每次要使用都去new一个是很不科学的。在设计模式中有一个专门的模式来解决这种场景下的问题，即享元模式。 享元模式其实很好理解，也就是构造一个对象池，这个对象池中维护一定数量的对象，需要的时候就从这个对象池中获取对象，使用完后返还给对象池。这样就避免构造对象所带来的耗时，提升了系统的性能。 举个栗子 之前的做法，虽然不会出什么问题，但是毕竟有线程在定时刷新，也是先当浪费资源的 private static List<InfluxDBConnection> conns = new LinkedList<InfluxDBConnection>(); static { Collections.synchronizedList(conns); final ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor(); service.scheduleWithFixedDelay(new Runnable() { public void run() { refreshConn(); } }, 0, 3, TimeUnit.MINUTES); } public synchronized static InfluxDBConnection getConn() { if (conns.size() <= 0) { refreshConn(); } InfluxDBConnection conn = null; try { conn = conns.get(0); } catch (Exception e) { } return conn; } private synchronized static void refreshConn() { if (conns.size() < 5) { InfluxDBConnection conn = createConn(); if (conn != null && conn.ping()) { conns.add(conn); } else { createInfluxConnErrEvent(); } } else { try { Thread.sleep(10000); } catch (Exception e) { } conns.get(0).close(); conns.remove(0); } } 5种对象池 common-pool2给我们提供了5种不同类型的对象池实现来给我们使用: GenericObjectPool SoftReferenceObjectPool GenericKeyedObjectPool ProxiedObjectPool ProxiedKeyedObjectPool 以GenericKeyedObjectPool为例 以GenericKeyedObjectPool为例的不同数据库连接对象池 Mysql 连接配置类 /** * Mysql连接配置 */ @Data public class MysqlConfig { private String host; private Integer port = 3306; private String username; private String password; private String database; private Date startTime; public MysqlConfig(String host, Integer port, String username, String password, String database) { this.host = host; this.port = port; this.username = username; this.password = password; this.database = database; this.startTime = new Date(); } } Mysql连接工厂 /** * Mysql连接工厂 */ @Slf4j public class MysqlConnectionFactory implements KeyedPooledObjectFactory<MysqlConfig, Connection> { @Override public PooledObject<Connection> makeObject(MysqlConfig mysqlConfig) throws Exception { System.out.println("makeObject: " + mysqlConfig.getHost()); Connection connection = null; try { String driver = "com.mysql.jdbc.Driver"; String url = "jdbc:mysql://" + mysqlConfig.getHost() + ":" + mysqlConfig.getPort() + "/" + mysqlConfig.getDatabase() + "?characterEncoding=utf8&useSSL=true"; Class.forName(driver); connection = DriverManager.getConnection(url, mysqlConfig.getUsername(), mysqlConfig.getPassword()); } catch (Exception e) { log.error("MysqlConnectionFactory makeObject exception: {}.", e.toString()); } if (!Objects.isNull(connection)) { return new DefaultPooledObject<Connection>(connection); } return null; } @Override public void destroyObject(MysqlConfig mysqlConfig, PooledObject<Connection> pooledObject) throws Exception { System.out.println("destroyObject: " + mysqlConfig.getHost()); Connection conn = pooledObject.getObject(); if (Objects.isNull(conn)) return; if (!conn.isClosed()) { conn.close(); } } @Override public boolean validateObject(MysqlConfig mysqlConfig, PooledObject<Connection> pooledObject) { Connection conn = pooledObject.getObject(); boolean isColsed = false; try { isColsed = conn.isClosed(); } catch (SQLException e) { isColsed = false; } boolean isInvalid = false; if (!isColsed) { Date startTime = mysqlConfig.getStartTime(); if (System.currentTimeMillis() - startTime.getTime() > 10 * 1000) { isInvalid = true; } } System.out.println("validateObject: " + mysqlConfig.getHost() + ",isclosed: " + isColsed + ", isInvalid: " + isInvalid + " [" + (System.currentTimeMillis() - mysqlConfig.getStartTime().getTime()) + "]"); if (!isColsed || isInvalid) { try { destroyObject(mysqlConfig, pooledObject); } catch (Exception e) { } } return !isColsed || isInvalid; } @Override public void activateObject(MysqlConfig mysqlConfig, PooledObject<Connection> pooledObject) throws Exception { System.out.println("activateObject: " + mysqlConfig.getHost()); // Connection conn = pooledObject.getObject(); // try { // String driver = "com.mysql.jdbc.Driver"; // String url = "jdbc:mysql://" + mysqlConfig.getHost() + ":" + mysqlConfig.getPort() + "/" // + mysqlConfig.getDatabase() + "?characterEncoding=utf8&useSSL=true"; // Class.forName(driver); // conn = DriverManager.getConnection(url, // mysqlConfig.getUsername(), // mysqlConfig.getPassword()); // } catch (Exception e) { // e.printStackTrace(); // } } @Override public void passivateObject(MysqlConfig mysqlConfig, PooledObject<Connection> pooledObject) throws Exception { //no method body } } Mysql连接池 /** * Mysql连接池 */ public class MysqlConnectionPool extends GenericKeyedObjectPool<MysqlConfig, Connection> { private static GenericKeyedObjectPool<MysqlConfig, Connection> pool = null; public MysqlConnectionPool(KeyedPooledObjectFactory<MysqlConfig, Connection> factory, GenericKeyedObjectPoolConfig<Connection> config) { super(factory, config); } public static GenericKeyedObjectPool<MysqlConfig, Connection> getInstance() { if (Objects.isNull(pool)) { initPool(); } return pool; } public synchronized static void initPool() { MysqlConnectionFactory factory = new MysqlConnectionFactory(); GenericKeyedObjectPoolConfig config = new GenericKeyedObjectPoolConfig(); config.setMaxIdlePerKey(10); config.setMaxTotalPerKey(100); config.setMaxTotal(500); config.setMinIdlePerKey(10); // config.setTestOnBorrow(true); config.setTestOnReturn(true); pool = new GenericKeyedObjectPool<>(factory, config); } } 测试： 每3秒会模拟即用归还连接，并打印pool的状态 public class Test { public static void main(String[] args) throws Exception { simulate(); printPoolInfo(); } private static void simulate() throws Exception { Thread t = new Thread() { public void run() { GenericKeyedObjectPool<MysqlConfig, Connection> pool = MysqlConnectionPool.getInstance(); MysqlConfig[] mysqlConfigArr = {new MysqlConfig("192.168.2.37", 3306, "root", "pigoss", "pigoss"), new MysqlConfig("192.168.2.219", 3306, "root", "pigoss", "pigoss")}; int i = 0; while (true) { MysqlConfig mysqlConfig = mysqlConfigArr[i % 2]; Connection connection = null; try { connection = pool.borrowObject(mysqlConfig); } catch (Exception e) { e.printStackTrace(); } ResultSet result = null; query(connection, "select count(*) from tuser;"); pool.returnObject(mysqlConfig, connection); try { Thread.sleep(3000); } catch (InterruptedException e) { } i++; } } }; t.start(); } private static void printPoolInfo() { Thread t = new Thread() { public void run() { while (true) { GenericKeyedObjectPool<MysqlConfig, Connection> pool = MysqlConnectionPool.getInstance(); synchronized (pool){ System.out.println("============"); System.out.println("CreatedCount: " + pool.getCreatedCount()); System.out.println("BorrowedCount: " + pool.getBorrowedCount()); System.out.println("ReturnedCount: " + pool.getReturnedCount()); System.out.println("DestroyedCount: " + pool.getDestroyedCount()); System.out.println(); } try { Thread.sleep(6000); } catch (InterruptedException e) { } } } }; t.start(); } private static void query(Connection connection, String sql) { ResultSet result = null; try { PreparedStatement ps = connection.prepareStatement(sql); result = ps.executeQuery(); while (result.next()) { System.out.println("user count: " + result.getString(1)); } } catch (Exception e) { } } } 测试结果 makeObject: 192.168.2.37 activateObject: 192.168.2.37 user count: 4 validateObject: 192.168.2.37,isclosed: false, isInvalid: false [373] destroyObject: 192.168.2.37 makeObject: 192.168.2.219 activateObject: 192.168.2.219 user count: 9 validateObject: 192.168.2.219,isclosed: false, isInvalid: false [3654] destroyObject: 192.168.2.219 ============ CreatedCount: 2 BorrowedCount: 2 ReturnedCount: 2 DestroyedCount: 0 NumIdle: 2
Comparison method violates its general contract!
之前项目上遇到的问题，从jdk6换到了jdk7，后台日志疯狂输出java.lang.IllegalArgumentException: Comparison method violates its general contract! 查阅资料得知，，在JDK7以后，实现Comparable接口实现自定义排序的时候，要满足一下三个特性： 自反性：x，y 的比较结果和 y，x 的比较结果相反。 传递性：x>y,y>z,则 x>z。 对称性：x=y,则 x,z 比较结果和 y，z 比较结果相同。 原因就是代码写的不严谨导致的，下面有两种解决方法： 方法一（推荐） 对代码做检查，对没有对null和0等不对称的判断的比较器加以完善。 方法二（不推荐） 设置System属性 System.setProperty("java.util.Arrays.useLegacyMergeSort", "true") 或者设置JVM参数 -Djava.util.Arrays.useLegacyMergeSort=true
最近项目支持需要远程用到微软的远程桌面，捣鼓了半天发现windows专业版支持远程桌面，家庭版是阉割了的（不能被远程），网上找到了工具以支持家庭版远程。 查看windows版本 win+r 输入 winver RDPWrap 工具 RDPWrap 是github开源的，可以放心使用 RDPWrap 传送门 解压后用管理员身份运行 install.bat，之后运行 RDPConf.exe 检测 全部为绿色则可以正常使用。 Not Listening 报红 重启TermService服务： net stop TermService net start TermService not supported 报红 参考issues： https://github.com/stascorp/rdpwrap/issues/898 替换 C:\Program Files\RDP Wrapper\rdpwrap.ini
首先 面向切面编程 AOP 即 Aspect Oriented Program 首先，在面向切面编程的思想里面，把功能分为核心业务功能，和周边功能。 所谓的核心业务，比如登陆，增加数据，删除数据都叫核心业务 所谓的周边功能，比如性能统计，日志，事务管理等等 周边功能在Spring的面向切面编程AOP思想里，即被定义为切面 在面向切面编程AOP的思想里面，核心业务功能和切面功能分别独立进行开发 然后把切面功能和核心业务功能 "编织" 在一起，这就叫AOP DEMO 一最近写的北京公交为例子吧，完成 操作日志 的功能 依赖 org.springframework.boot spring-boot-starter-aop 定义日志注解 @Retention(RetentionPolicy.RUNTIME) 什么时候使用该注解，我们定义为运行时； @Target({ ElementType.METHOD }) 注解用于什么地方，我们定义为作用于方法上； 注解名为 OptLogAnnotation; 定义一个属性 日志描述信息 ，默认为空字符串； package cn.yiidii.panel.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 日志注解 */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface OptLogAnnotation { String desc() default "";//日志描述信息 } 定义操作日志切面 aspectj 相关注解的作用 @Aspect：声明该类为一个注解类； @Pointcut：定义一个切点，后面跟随一个表达式，表达式可以定义为切某个注解，也可以切某个 package 下的方法； 切点定义好后，就是围绕这个切点做文章了： @Before: 在切点之前，织入相关代码； @After: 在切点之后，织入相关代码; @AfterReturning: 在切点返回内容后，织入相关代码，一般用于对返回值做些加工处理的场景； @AfterThrowing: 用来处理当织入的代码抛出异常后的逻辑处理; @Around: 环绕，可以在切入点前后织入代码，并且可以自由的控制何时执行切点； package cn.yiidii.panel.advice; import cn.yiidii.panel.annotation.OptLogAnnotation; import cn.yiidii.panel.common.utils.IPUtil; import cn.yiidii.panel.pojo.OptLog; import cn.yiidii.panel.pojo.User; import cn.yiidii.panel.service.impl.OptLogService; import cn.yiidii.panel.shiro.util.SecurityUtil; 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.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Method; import java.util.Date; import java.util.Objects; /** * 操作日志切面 */ @Aspect @Component @Slf4j public class OptLogAdvice { @Autowired private HttpServletRequest request; @Autowired private OptLogService optLogService; @Around(value = "@annotation(cn.yiidii.panel.annotation.OptLogAnnotation)")//已注解 @OptLogAnnotation 为切点 public void insertOptLog(ProceedingJoinPoint joinPoint) throws Throwable { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod();//注解的方法 OptLogAnnotation logAnnotation = method.getAnnotation(OptLogAnnotation.class);//注解 OptLog optLog = packageOptLog(logAnnotation.desc(), "NO_MODULE"); optLogService.insert(optLog); } private OptLog packageOptLog(String content, String module) { OptLog optLog = new OptLog(); User user = SecurityUtil.getCurrUser(); if (!Objects.isNull(user)) { optLog.setUid(user.getId()); optLog.setSubject(user.getUsername()); } String ip = IPUtil.getIpAddr(request); optLog.setIp(ip); optLog.setLocationInfo(IPUtil.getLocationByIP(ip)); optLog.setModule(module); optLog.setContent(content); optLog.setCreateTime(new Date()); return optLog; } } 效果
在某火看到的云函数很火，签到无非是调用接口，利用云函数定时执行，下面是简单的使用教程。 获取 Cookie 利用抓包工具，安卓 HttpCanary , IOS thor， 电脑 Fiddler，获取自己的cookie 新建云函数 在 腾讯云函数控制台 添加云函数，设置运行函数为php 函数代码 <?php function main_handler($event,$time){ //脚本设置 $phone = ''; //填写手机号 $cookie = ''; //填写cookies $sckey = ''; //填写sever酱sckey $data = 'className=signinIndex'; $date = date('YmdHis'); $headers = array( "Referer: https://act.10010.com/SigninApp/signin/querySigninActivity.htm?desmobile=".$phone."&version=android@7.0000", "Accept: text/html, application/xhtml+xml, image/jxr, */*", "User-Agent: Mozilla/5.0 (Linux; Android 9; MI MAX 3 Build/PKQ1.180729.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.157 Mobile Safari/537.36; unicom{version:android@7.0000,desmobile:15690507717};devicetype{deviceBrand:Xiaomi,deviceModel:MI MAX 3}" ); $random = mt_rand(100000,999999); $renzheng = post('https://act.10010.com/SigninApp/signin/rewardReminder.do?vesion=0.018936113139'.$random,' ',$cookie,$headers); //金币查询 $count1 = post('https://act.10010.com/SigninApp/signin/goldTotal.do?vesion=0.019762071301'.$random,' ',$cookie,$headers); //签到 $output = post('https://act.10010.com/SigninApp/signin/daySign.do',$data,$cookie,$headers); $qd1 = json_decode($output,true); if($qd1['msgCode'] == '0000'){ $qd1_result = '签到成功，获得了'.$qd1['continuCount'].'金币'; }elseif($qd1['msgCode'] == '0008'){ $qd1_result = '签到失败,今天已签到!'; }else{ $qd1_result = '签到失败,cookie过期。'; file_get_contents('https://sc.ftqq.com/'.$sckey.'.send?text='.urlencode('联通营业厅签到cookie过期啦!')); return 'error'; } //微博签到 $wb = post('https://act.10010.com/signinAppH/commonTask?vesion=0.01839278081'.$random,'transId='.$data.'0.19402970817'.$random.'&userNumber='.$phone.'&taskCode=TA590934984&finishTime=20191213174953&taskType=DAILY_TASK',$cookie,$headers); $qd2 = json_decode($wb,true); if($qd2['respCode'] == '0000'){ $qd2_result = '签到成功'; }elseif($qd2['respCode'] == '0003'){ $qd2_result = '签到失败,今天已签到!'; }else{ $qd2_result = '签到失败,cookie过期。'; } //金币查询 $count2 = post('https://act.10010.com/SigninApp/signin/goldTotal.do?vesion=0.019762071301'.$random,' ',$cookie,$headers); echo '脚本执行完成！营业厅签到结果：'.$qd1_result.'。微博双签结果：'.$qd2_result.'。[签到前金币:'.$count1.', 签到后金币:'.$count2; } function post($url, $data, $cookie,$headers) { //初使化init方法 $ch = curl_init(); //指定URL curl_setopt($ch, CURLOPT_URL, $url); //设定请求后返回结果 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); //声明使用POST方式来进行发送 curl_setopt($ch, CURLOPT_POST, 1); //发送什么数据呢 curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_COOKIE, $cookie); //忽略证书 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_HTTPHEADER,$headers); //设置超时时间 curl_setopt($ch, CURLOPT_TIMEOUT, 10); //发送请求 $output = curl_exec($ch); //关闭curl curl_close($ch); //返回数据 return $output; } ?> 当然也可以填写自己的 sever酱sckey 实现微信推送。 触发方式 添加定时触发方式，根据自己需求填写cron表达式，我这里每天0点执行。 效果
由于把shiro改造成前后台分离的Restful风格, 启动项目，duang~~ #(不高兴) 百度 --> 经典的跨域问题 何为跨域？ Url 的一般格式： 协议 + 域名（子域名 + 主域名） + 端口号 + 资源地址 示例： https://www.dustyblog.cn:8080/say/Hello 是由 https + www + dustyblog.cn + 8080 + say/Hello 组成。 只要协议，子域名，主域名，端口号这四项组成部分中有一项不同，就可以认为是不同的域，不同的域之间互相访问资源，就被称之为跨域。 只介绍 springboot 通用的解决方案 解决跨域的方法有很多种： 注解驱动 （@CrossOrigin 注解） 过滤器 或 拦截器实现 （在 response 中写入所需要的响应头） CORS全局配置 （springboot 服务中心添加一个配置文件） 介绍下第三种方式 新建跨域配置类：CorsConfig.java package cn.yiidii.panel.config; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; @Configuration @Slf4j public class CorsConfig { @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", buildConfig()); // 4 return new CorsFilter(source); } private CorsConfiguration buildConfig() { log.debug("*************************CorsConfig buildConfig"); CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.addAllowedOrigin("*"); // 1允许任何域名使用 corsConfiguration.addAllowedHeader("*"); // 2允许任何头 corsConfiguration.addAllowedMethod("*"); // 3允许任何方法（post、get等） corsConfiguration.setAllowCredentials(true);//支持安全证书。跨域携带cookie需要配置这个 corsConfiguration.setMaxAge(3600L);//预检请求的有效期，单位为秒。设置maxage，可以避免每次都发出预检请求 return corsConfiguration; } } 完美解决！ 转载： 天工开物_152368 - Spring Boot如何解决前端的Access-Control-Allow-Origin跨域问题
/** * Shiro过滤器配置 */ @Bean(name = "shiroFilter") public ShiroFilterFactoryBean shiroFilter() { ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean(); // Shiro的核心安全接口,这个属性是必须的 shiroFilter.setSecurityManager(securityManager()); // shiroFilter.setLoginUrl("");//身份认证失败，则跳转到登录页面的配置 没有登录的用户请求需要登录的页面时自动跳转到登录页面，不是必须的属性，不输入地址的话会自动寻找项目web项目的根目录下的”/login.jsp”页面。 // shiroFilter.setSuccessUrl("");//登录成功默认跳转页面，不配置则跳转至”/”。如果登陆前点击的一个需要登录的页面，则在登录自动跳转到那个需要登录的页面。不跳转到此。 // shiroFilter.setUnauthorizedUrl("");//没有权限默认跳转的页面 // shiroFilter.setFilterChainDefinitions("");//filterChainDefinitions的配置顺序为自上而下,以最上面的为准 //自定义过滤 //oauth2 Map<String, Filter> filters = new HashMap<>(16); filters.put("oauth2", new Oauth2Filter()); shiroFilter.setFilters(filters); //当运行一个Web应用程序时,Shiro将会创建一些有用的默认Filter实例,并自动地在[main]项中将它们置为可用自动地可用的默认的Filter实例是被DefaultFilter枚举类定义的,枚举的名称字段就是可供配置的名称 /** * anon---------------org.apache.shiro.web.filter.authc.AnonymousFilter 没有参数，表示可以匿名使用。 * authc--------------org.apache.shiro.web.filter.authc.FormAuthenticationFilter 表示需要认证(登录)才能使用，没有参数 * authcBasic---------org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter 没有参数表示httpBasic认证 * logout-------------org.apache.shiro.web.filter.authc.LogoutFilter * noSessionCreation--org.apache.shiro.web.filter.session.NoSessionCreationFilter * perms--------------org.apache.shiro.web.filter.authz.PermissionAuthorizationFilter 参数可以写多个，多个时必须加上引号，并且参数之间用逗号分割，例如/admins/user/**=perms["user:add:*,user:modify:*"]，当有多个参数时必须每个参数都通过才通过，想当于isPermitedAll()方法。 * port---------------org.apache.shiro.web.filter.authz.PortFilter port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等，serverName是你访问的host,8081是url配置里port的端口，queryString是你访问的url里的？后面的参数。 * rest---------------org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter 根据请求的方法，相当于/admins/user/**=perms[user:method] ,其中method为post，get，delete等。 * roles--------------org.apache.shiro.web.filter.authz.RolesAuthorizationFilter 参数可以写多个，多个时必须加上引号，并且参数之间用逗号分割，当有多个参数时，例如admins/user/**=roles["admin,guest"],每个参数通过才算通过，相当于hasAllRoles()方法。 * ssl----------------org.apache.shiro.web.filter.authz.SslFilter 没有参数，表示安全的url请求，协议为https * user---------------org.apache.shiro.web.filter.authz.UserFilter 没有参数表示必须存在用户，当登入操作时不做检查 */ /** * 通常可将这些过滤器分为两组 * anon,authc,authcBasic,user是第一组认证过滤器 * perms,port,rest,roles,ssl是第二组授权过滤器 * 注意user和authc不同：当应用开启了rememberMe时,用户下次访问时可以是一个user,但绝不会是authc,因为authc是需要重新认证的 * user表示用户不一定已通过认证,只要曾被Shiro记住过登录状态的用户就可以正常发起请求,比如rememberMe 说白了,以前的一个用户登录时开启了rememberMe,然后他关闭浏览器,下次再访问时他就是一个user,而不会authc * * * 举几个例子 * /admin=authc,roles[admin] 表示用户必需已通过认证,并拥有admin角色才可以正常发起'/admin'请求 * /edit=authc,perms[admin:edit] 表示用户必需已通过认证,并拥有admin:edit权限才可以正常发起'/edit'请求 * /home=user 表示用户不一定需要已经通过认证,只需要曾经被Shiro记住过登录状态就可以正常发起'/home'请求 */ /** * 各默认过滤器常用如下(注意URL Pattern里用到的是两颗星,这样才能实现任意层次的全匹配) * /admins/**=anon 无参,表示可匿名使用,可以理解为匿名用户或游客 * /admins/user/**=authc 无参,表示需认证才能使用 * /admins/user/**=authcBasic 无参,表示httpBasic认证 * /admins/user/**=ssl 无参,表示安全的URL请求,协议为https * /admins/user/**=perms[user:add:*] 参数可写多个,多参时必须加上引号,且参数之间用逗号分割,如/admins/user/**=perms["user:add:*,user:modify:*"]。当有多个参数时必须每个参数都通过才算通过,相当于isPermitedAll()方法 * /admins/user/**=port[8081] 当请求的URL端口不是8081时,跳转到schemal://serverName:8081?queryString。其中schmal是协议http或https等,serverName是你访问的Host,8081是Port端口,queryString是你访问的URL里的?后面的参数 * /admins/user/**=rest[user] 根据请求的方法,相当于/admins/user/**=perms[user:method],其中method为post,get,delete等 * /admins/user/**=roles[admin] 参数可写多个,多个时必须加上引号,且参数之间用逗号分割,如：/admins/user/**=roles["admin,guest"]。当有多个参数时必须每个参数都通过才算通过,相当于hasAllRoles()方法 * */ //Shiro验证URL时,URL匹配成功便不再继续匹配查找(所以要注意配置文件中的URL顺序,尤其在使用通配符时) // 配置不会被拦截的链接 顺序判断 Map<String, String> filterMap = new LinkedHashMap<>(); filterMap.put("/favicon.ico", "anon"); filterMap.put("/webjars/**", "anon"); filterMap.put("/web/**", "anon"); filterMap.put("/login", "anon"); //所有请求需要oauth2认证 filterMap.put("/**", "oauth2"); shiroFilter.setFilterChainDefinitionMap(filterMap); return shiroFilter; } spring中 shiro logout 配置方式 普通的 实现自己的logout方法，取到Subject，然后logout。 使用shiro提供的logout filter