Spring Boot+MyBatis+SQLite配置例子参考下面
ubuntu 18.04完整安装配置WordPress 5.5.3并调优
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
$ sudo apt-get install apache2 $ sudo apt-get install mysql-server $ sudo apt-get install mysql-client $ sudo apt-get install php7.2 $ sudo apt-get install php-gd $ sudo apt install php-mbstring $ sudo apt install php-dom # 如果使用了WP-Statistics统计插件,需要安装依赖 $ sudo apt-get install php7.2-curl $ sudo apt-get install php7.2-bcmath # PHP Zip支持,提高网络以及内存压缩工具,提升性能 $ sudo apt-get install php-zip # PHP图像处理支持,imagick替代默认的GD图像处理,提升图像处理性能 $ sudo apt install php-imagick # 默认imagick是不启用的,需要手工开启 $ sudo phpenmod imagick $ sudo a2dismod mpm_prefork $ sudo a2enmod mpm_event $ sudo apt-get install libapache2-mod-fastcgi php7.2-fpm $ sudo service php7.2-fpm restart $ sudo a2enmod actions fastcgi alias proxy_fcgi $ sudo apt-get install php-mysql # 启用 Rewrite 模块,我们后续的WP Super Cache需要这个模块的支持 $ sudo a2enmod rewrite $ sudo service apache2 restart # 我们以root连接数据库,我们需要手工创建数据库,否则会出现如下错误: # “我们能够连接到数据库服务器(这意味着您的用户名和密码正确),但未能选择wordpress数据库。” $ mysql -u root -p -e "create database wordpress;" $ cd /var/www $ sudo chown -R www-data:www-data wordpress |
WordPress配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
<VirtualHost *:80> # The ServerName directive sets the request scheme, hostname and port that # the server uses to identify itself. This is used when creating # redirection URLs. In the context of virtual hosts, the ServerName # specifies what hostname must appear in the request's Host: header to # match this virtual host. For the default virtual host (this file) this # value is not decisive as it is used as a last resort host regardless. # However, you must set it for any further virtual host explicitly. #ServerName www.example.com ServerAdmin webmaster@localhost #DocumentRoot /var/www/html DocumentRoot /var/www/wordpress <Directory /var/www/wordpress> #Options Indexes FollowSymLinks MultiViews Options FollowSymLinks MultiViews AllowOverride All # Apache 2.2 # FCGIWrapper /usr/bin/php5-cgi .php # AddHandler fcgid-script .php # Options ExecCGI SymLinksIfOwnerMatch # Apache 2.4.10 <FilesMatch \.php$> SetHandler "proxy:unix:/run/php/php7.2-fpm.sock|fcgi://localhost" </FilesMatch> Order allow,deny allow from all </Directory> # Available loglevels: trace8, ..., trace1, debug, info, notice, warn, # error, crit, alert, emerg. # It is also possible to configure the loglevel for particular # modules, e.g. #LogLevel info ssl:warn ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined # For most configuration files from conf-available/, which are # enabled or disabled at a global level, it is possible to # include a line for only one particular virtual host. For example the # following line enables the CGI configuration for this host only # after it has been globally disabled with "a2disconf". #Include conf-available/serve-cgi-bin.conf </VirtualHost> # vim: syntax=apache ts=4 sw=4 sts=4 sr noet |
启用Apache2
的cache
,expire
,gzip
模块,加强服务器性能
1 2 3 4 5 6 7 |
$ sudo a2enmod cache $ sudo a2enmod expires $ sudo a2enmod deflate $ sudo service apache2 restart |
PHP-FPM进程数的设定,提高响应速度,解决页面加载时候缓慢的问题(如果感觉打开页面缓慢,建议调整下面的配置,这种情况尤其在访问量不大的时候会出现短暂的耗时增大,原因就是php进程被临时创建导致延迟增大)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# 增大最大并发进程数量,默认为5 根据需要增大 $ sudo sed -i "s/^pm.max_children = 5/pm.max_children = 20/g" /etc/php/7.2/fpm/pool.d/www.conf # 增大系统启动初始化的进程数量,默认为 1 根据需要增大 $ sudo sed -i "s/^pm.start_servers = 1/pm.start_servers = 10/g" /etc/php/7.2/fpm/pool.d/www.conf # 增大保留的最低空闲进程数量,默认为 2 根据需要增大 $ sudo sed -i "s/^pm.min_spare_servers = 2/pm.min_spare_servers = 10/g" /etc/php/7.2/fpm/pool.d/www.conf # 增大保留的最高空闲进程数量,默认为 3 根据需要增大 $ sudo sed -i "s/^pm.max_spare_servers = 3/pm.max_spare_servers = 20/g" /etc/php/7.2/fpm/pool.d/www.conf # 配置进程在处理多少个请求之后就退出,避免某些操作导致内存泄漏,及时进行资源回收操作 $ sudo sed -i "s/^#pm.max_requests = 500/pm.max_requests = 5000/g" /etc/php/7.2/fpm/pool.d/www.conf |
启用PHP7的opcache配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
######避免PHP信息暴露在http头中 expose_php = Off ######在关闭display_errors后开启PHP错误日志(路径在php-fpm.conf中配置) log_errors = On ######开启opcache [opcache] ; Determines if Zend OPCache is enabled ;opcache.enable=0 opcache.enable=1 ; The OPcache shared memory storage size. ;opcache.memory_consumption=128 opcache.memory_consumption=256 ; The maximum number of keys (scripts) in the OPcache hash table. ; Only numbers between 200 and 1000000 are allowed. ;opcache.max_accelerated_files=10000 opcache.max_accelerated_files=1000000 ; When disabled, you must reset the OPcache manually or restart the ; webserver for changes to the filesystem to take effect. ;opcache.validate_timestamps=1 opcache.validate_timestamps=3000 |
重启服务
1 |
$ sudo service php7.2-fpm restart |
参考链接
HttpClient利用MultipartEntityBuilder取代MultipartEntity上传图片文件到服务器
注意:在HttpClient4.3之后,原来的上传文件方法MultipartEntity已经不建议使用,现替换成新的httpmime下面的MultipartEntityBuilder。
需要添加httpclient-4.5.jar、httpmime-4.5.jar两个包
maven添加:
1 2 3 4 5 6 7 8 9 10 |
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpmime</artifactId> <version>4.5</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5</version> </dependency> |
下面我们对比一下MultipartEntity和MultipartEntityBuilder的使用区别:
MultipartEntityBuilder:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
//传入参数可以为file或者filePath,在此处做转换 File file = new File(filePath); CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse httpResponse = null; HttpPost httppost = new HttpPost(url); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); //设置浏览器兼容模式 builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); //设置请求的编码格式 builder.setCharset(Consts.UTF_8); builder.setContentType(ContentType.MULTIPART_FORM_DATA); //添加文件 builder.addBinaryBody("file", file); HttpEntity reqEntity = builder.build(); httppost.setEntity(reqEntity); httpResponse = httpClient.execute(httppost); |
MultipartEntity:
1 2 3 4 5 6 7 8 9 10 |
//传入参数可以为file或者filePath,在此处做转换 File file = new File(filePath); CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse httpResponse = null; HttpPost httppost = new HttpPost(url); FileBody filebody = new FileBody(file); MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE, null, Charset.forName("UTF-8")); entity.addPart("file", filebody); httppost.setEntity(entity); |
直接上替换后的测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
package com.test.util; import net.sf.json.JSONObject; import org.apache.commons.lang.StringUtils; import org.apache.http.Consts; import org.apache.http.HttpEntity; import org.apache.http.HttpStatus; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentType; import org.apache.http.entity.mime.HttpMultipartMode; import org.apache.http.entity.mime.MultipartEntityBuilder; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import java.io.*; import java.net.HttpURLConnection; import java.net.URL; import java.util.Iterator; import java.util.Map; public class HttpPostUploadUtil { /** * @param args */ public static void main(String[] args) { String filePath = "D:\\Test\\0.jpg"; String url = "http://127.0.0.1:8080/FastFds"; //调用上传方法 String backInfo = uploadPost(url, filePath); if(StringUtils.isNotBlank(backInfo)){ //转json数据 JSONObject json = JSONObject.fromObject(backInfo); if(!json.isEmpty()){ //数据处理 String value = json.getString("test"); System.out.println(value); } } } /** * 上传图片/文件 * @param url * @param filePath * @return String 用于转json或者其他信息 */ public static String uploadPost(String url, String filePath) { String requestJson = ""; //传入参数可以为file或者filePath,在此处做转换 File file = new File(filePath); CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse httpResponse = null; try { HttpPost httppost = new HttpPost(url); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); //设置浏览器兼容模式 builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); //设置请求的编码格式 builder.setCharset(Consts.UTF_8); builder.setContentType(ContentType.MULTIPART_FORM_DATA); //添加文件 builder.addBinaryBody("file", file); HttpEntity reqEntity = builder.build(); httppost.setEntity(reqEntity); httpResponse = httpClient.execute(httppost); int backCode = httpResponse.getStatusLine().getStatusCode(); if(backCode == HttpStatus.SC_OK){ HttpEntity httpEntity = httpResponse.getEntity(); byte[] json= EntityUtils.toByteArray(httpEntity); requestJson = new String(json, "UTF-8"); //关闭流 EntityUtils.consume(httpEntity); return requestJson; } } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { //释放资源 try { httpClient.close(); httpResponse.close(); } catch (IOException e) { e.printStackTrace(); } } return null; } } |
继续阅读HttpClient利用MultipartEntityBuilder取代MultipartEntity上传图片文件到服务器
正则表达式 - 字符匹配不以某字段开头或者结尾
正则表达式 - 字符匹配不以某字段开头或者结尾
最近有一个需求,要求是判断某个字符串不以什么开头。然后就开始探索这个不以什么开头和不易什么结尾的正则怎么写,why?
不以某字符串开头
这里需要提一个概念叫 否定式前项匹配
这个东东。
向前匹配
根据匹配的字符序列后面存在一个特定的字符序列
或者不存在一个特定的序列
来决定是否匹配。对于向前匹配,出现在指定项后面的字符序列不会被正则表达式返回。
这里说后面存在一个特定字符序列, 也称之为肯定式向前查找
不存在一个特定的序列,也称之为否定式向前查找
这两个概念后续再深入
开始解决这个问题不以某字符串开头:
1 |
^(?!str) |
这里使用了下面几个元符号:
^
判断是否是开头?!
这里是否定向前查询
示例:
- 不以test 开头字符串
1 |
/^(?!test).*/ |
Spring Boot实现异步任务
Spring Boot中的另外一个任务:异步任务。所谓异步任务,其实就是异步执行程序,有些时候遇到一些耗时的的任务,如果一直卡等待,肯定会影响其他程序的执行,所以就让这些程序需要以异步的方式去执行。那么下面就来介绍Spring Boot 如何实现异步任务。
一、使用注解@EnableAsync 开启异步调用方法
在application启动类中,加上@EnableAsync注解,Spring Boot 会自动扫描异步任务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
package com.weiz; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; import tk.mybatis.spring.annotation.MapperScan; @SpringBootApplication //扫描 mybatis mapper 包路径 @MapperScan(basePackages = "com.weiz.mapper") //扫描 所有需要的包, 包含一些自用的工具类包 所在的路径 @ComponentScan(basePackages = {"com.weiz","org.n3r.idworker"}) //开启定时任务 @EnableScheduling //开启异步调用方法 @EnableAsync public class SpringBootStarterApplication { public static void main(String[] args) { SpringApplication.run(SpringBootStarterApplication.class, args); } } |
二、创建异步执行类,定义@Component及@Async组件
创建com.weiz.tasks包,在tasks包里增加AsyncTask 异步任务类,加上@Component 注解,然后在需要异步执行的方法前面加上@Async注解,这样Spring Boot容器扫描到相关异步方法之后,调用时就会将这些方法异步执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
package com.weiz.tasks; import java.util.concurrent.Future; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.AsyncResult; import org.springframework.stereotype.Component; @Component public class AsyncTask { @Async public Future<Boolean> doTask11() throws Exception { long start = System.currentTimeMillis(); Thread.sleep(1000); long end = System.currentTimeMillis(); System.out.println("任务1耗时:" + (end - start) + "毫秒"); return new AsyncResult<>(true); } @Async public Future<Boolean> doTask22() throws Exception { long start = System.currentTimeMillis(); Thread.sleep(700); long end = System.currentTimeMillis(); System.out.println("任务2耗时:" + (end - start) + "毫秒"); return new AsyncResult<>(true); } @Async public Future<Boolean> doTask33() throws Exception { long start = System.currentTimeMillis(); Thread.sleep(600); long end = System.currentTimeMillis(); System.out.println("任务3耗时:" + (end - start) + "毫秒"); return new AsyncResult<>(true); } } |
说明:@Async 加上这个注解,就表示该方法是异步执行方法。
三、调用
创建一个DoTask调用类,我们看看这几个方法,是怎么执行的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
package com.weiz.tasks; import java.util.concurrent.Future; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("tasks") public class DoTask { @Autowired private AsyncTask asyncTask; @RequestMapping("test1") public String test1() throws Exception { long start = System.currentTimeMillis(); Future<Boolean> a = asyncTask.doTask11(); Future<Boolean> b = asyncTask.doTask22(); Future<Boolean> c = asyncTask.doTask33(); while (!a.isDone() || !b.isDone() || !c.isDone()) { if (a.isDone() && b.isDone() && c.isDone()) { break; } } long end = System.currentTimeMillis(); String times = "任务全部完成,总耗时:" + (end - start) + "毫秒"; System.out.println(times); return times; } } |
SpringBoot如何实现一个实时更新的进度条的示例代码
前言
导入excel表格批量修改状态,期间如果发生错误则所有数据不成功,为了防止重复提交,做一个类似进度条的东东。
那么下面我会结合实际业务对这个功能进行分析和记录。
正文
思路
前端使用bootstrap,后端使用SpringBoot分布式到注册中心,原先的想法是导入表格后异步调用修改数据状态的方法,然后每次计算修改的进度然后存放在session中,前台jquery写定时任务访问获取session中的进度,更新进度条进度和百分比。但是这存在session在服务间不共享,跨域问题。那么换一个方式存放,存放在redis中,前台定时任务直接操作获取redis的数据。
实施
进度条
先来看一下bootstrap的进度条
1 2 3 4 5 6 7 |
<div class="progress progress-striped active"> <div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100" style="width: 40%;"> 40% </div> </div> |
进度条更新主要更新style="width: 40%;"的值即可,div里面的40%可以省略,无非时看着明确。
可以考虑将进度条放入弹出层。
定时任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
//点击确认导入执行此方法 function bulkImportChanges() { //获取批量操作状态文件 var files = $("#importChanges").prop("files"); var changesFile = files[0]; var formData = new FormData(); formData.append("importFile",changesFile); $.ajax({ type : 'post', url : "/risk/bulk***es", data : formData, processData : false, //文件ajax上传要加这两个的,要不然上传不了 contentType : false, // success : function(obj) { //导入成功 if (obj.rspCode == "00") { //定时任务获取redis导入修改进度 var progress = ""; var timingTask = setInterval(function(){ $.ajax({ type: 'post', url: "/risk/t***k", dataType : 'json', success: function(result) { progress = result.value; if (progress != "error"){ var date = progress.substring(0,6); //这里更新进度条的进度和数据 $(".progress-bar").width(parseFloat(date)+"%"); $(".progress-bar").text(parseFloat(date)+"%"); } } }); //导入修改完成或异常(停止定时任务) if (parseInt(progress)==100 || progress == "error") { //清除定时执行 clearInterval(timingTask); $.ajax({ type: 'post', url: "/risk/de***ess", dataType : 'json', success: function(result) { $("#bulkImportChangesProcessor").hide(); if (parseInt(progress) == 100) { alert("批量导入修改状态成功"); } if (progress == "error") { alert("批量导入修改状态失败"); } //获取最新数据 window.location.href="/risk/re***ByParam" rel="external nofollow" rel="external nofollow" ; } }); } }, 1000) }else { $("#bulkImportChangesProcessor").hide(); alert(obj.rspMsg); window.location.href="/risk/re***ByParam" rel="external nofollow" rel="external nofollow" ; } } }); } |
解释:点击确认导入文件后成功后开启定时任务每一秒(一千毫秒)访问一次后台获取redis存放的进度,返回更新进度条,如果更新完成或者更新失败(根据后台返回的数据决定)则停止定时任务显示相应的信息并刷新页面。获取最新数据。
后台控制层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
/** * 退单管理批量修改状态导入文件 * @param importFile * @return */ @ResponseBody @RequestMapping("/bulk***es") public Map<String,Object> bulk***es(MultipartFile importFile){ log.info("退单管理批量修改状态导入文件,传入参数:"+importFile); Map<String,Object> map = new HashMap<>(); List<Bulk***esEntity> fromExcel = null; try{ //使用工具类导入转成list String[] header = {"sy***um","t***mt","ha***ult","re***nd","sy***nd","r**k"}; fromExcel = importExcelUtil.importDataFromExcel(importFile, header, BulkImportChangesEntity.class); if (fromExcel.size()==0){ map.put("rspCode","99"); map.put("rspMsg","导入数据不能为空"); return map; } }catch (Exception e){ map.put("rspCode","99"); map.put("rspMsg","导入操作表失败,请注意数据列格式"); return map; } try { //这里会对list集合中的数据进行处理 log.info("调用服务开始,参数:"+JSON.toJSONString(fromExcel)); //String url = p4_zuul_url+"/***/ri***eat/bu***nges"; String url = p4_zuul_url+"/***-surpass/ri***eat/bu***nges"; String result = HttpClientUtil.doPost(url,JSON.toJSONString(fromExcel)); log.info("调用服务结束,返回数据:"+result); if (result != null){ map = JSONObject.parseObject(result, Map.class); log.info("批量修改状态导入:"+JSON.toJSONString(map)); } }catch (Exception e){ map.put("rspCode","99"); map.put("rspMsg","导入操作表失败"); log.info("bu***es exception",e); return map; } return map; } /** * 获取退单管理批量修改状态导入文件进度条进度 * @return */ @ResponseBody @RequestMapping("/t***sk") public Map<String,Object> t***sk(){ Map<String,Object> map = new HashMap<>(); //获取redis值 String progress = HttpClientUtil.doGet( p4_zuul_url + "/" + p4_redis + "/redis***ler/get?key=progressSchedule"); if (progress != null){ map = JSONObject.parseObject(progress, Map.class); log.info("进度条进度:"+JSON.toJSONString(map)); map.put("progressSchedule",progress); }else { HttpClientUtil.doGet( p4_zuul_url + "/" + p4_redis + "/redis***ler/del?key=progressSchedule"); } return map; } /** * 清除redis进度条进度 * @return */ @ResponseBody @RequestMapping("/de***ess") public Map<String,Object> de***ess(){ Map<String,Object> map = new HashMap<>(); String progress = HttpClientUtil.doGet( p4_zuul_url + "/" + p4_redis + "/redis***ler/del?key=progressSchedule"); if (progress != null){ map = JSONObject.parseObject(progress, Map.class); log.info("返回数据:"+JSON.toJSONString(map)); } return map; } |
导入时调用第一个bulk***es方法,定时任务调用t***sk方法,导入完成或发生错误调用de***ess方法删除redis数据,避免占用资源。
服务层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
@Async//开启异步 @Transactional(rollbackFor = Exception.class)//事务回滚级别 @Override public void bulkImportChanges(List<BulkImportChangesParam> list) { //初始化进度 Double progressBarSchedule = 0.0; redisClient.set("progressSchedule", progressBarSchedule + "");//存redis try { for (int i = 1; i <= list.size(); i++) { RiskRetreatEntity entity = riskRetreatMapper.selectRetreatListBySysRefNum(list.get(i-1).getSysRefNum()); if (entity == null){ //查询结果为空直接进行下次循环不抛出 continue; } //实体封装 ··· //更新 riskRetreatMapper.updateRetreatByImport(entity); //计算修改进度并存放redis保存(1.0 / list.size())为一条数据进度 progressBarSchedule = (1.0 / list.size()) * i*100; redisClient.set("progressSchedule", progressBarSchedule+""); if (i==list.size()){ redisClient.set("progressSchedule", "100"); } } }catch (Exception e){ //当发生错误则清除缓存直接抛出回滚 redisClient.set("progressSchedule","error"); log.info("导入更新错误,回滚"); log.info("bulkImportChanges exception:",e); throw e; } } |
每更新一条数据存放进度,当发生错误则进行回滚。如果开启异步则需要在启动类添加注解@EnableAsync。
1 2 3 4 5 6 7 |
@EnableAsync ···//其他注解 public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } |
认识ARM64汇编
之前说过学习汇编就是学习寄存器和指令,查看代码请连接真机。
寄存器
在arm64
汇编中寄存器是64
bit的,使用X[n]
表示,低32位以w[n]
表示

在64
位架构中有31
个64
位的通用寄存器。
可以通过register read
查看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
(lldb) register read General Purpose Registers: x0 = 0x0000000000000001 x1 = 0x00000002805d8a20 x2 = 0x00000002837e7980 x3 = 0x00000002805d8a20 x4 = 0x0000000000000001 x5 = 0x0000000000000001 x6 = 0x0000000100d54000 x7 = 0x0000000000000000 x8 = 0x0000200000000000 x9 = 0x000161a1d63f7945 (0x00000001d63f7945) (void *)0x01d63f7cb0000001 x10 = 0x0000000000000000 x11 = 0x000000000000006d x12 = 0x000000000000006d x13 = 0x0000000000000000 x14 = 0x0000000000000000 x15 = 0x00000001ca634e6d "touchesBegan:withEvent:" x16 = 0x000000019c4cd47c libobjc.A.dylib`objc_storeStrong x17 = 0x0000000000000000 x18 = 0x0000000000000000 x19 = 0x0000000100f17810 x20 = 0x00000002837e7920 x21 = 0x00000002805d8a20 x22 = 0x00000001ca634e6d "touchesBegan:withEvent:" x23 = 0x0000000100e11a30 x24 = 0x00000002837e7980 x25 = 0x0000000100e11a30 x26 = 0x00000002805d8a20 x27 = 0x00000001ca62d900 "countByEnumeratingWithState:objects:count:" x28 = 0x00000002805d8a20 fp = 0x000000016f47d730 lr = 0x00000001009866dc ArmAssembly`-[ViewController touchesBegan:withEvent:] + 84 at ViewController.m:38 sp = 0x000000016f47d720 pc = 0x0000000100986720 ArmAssembly`foo1 + 16 at ViewController.m:46 cpsr = 0x80000000 (lldb) |
WP Statistics插件在开启缓存功能后无法统计访问量
WP Statistics插件在开启缓存功能后无法统计访问量。
跟踪发现统计访问量使用的url返回401错误,如下:
1 2 3 |
https://www.mobibrw.com/wp-json/wpstatistics/v1/hit?_=1603432478&_wpnonce=1b5c35aa31&wp_statistics_hit_rest=yes&ua=Mozilla/5.0%20(Macintosh;%20Intel%20Mac%20OS%20X%2010_15_7)%20AppleWebKit/605.1.15%20(KHTML,%20like%20Gecko)%20Version/14.0%20Safari/605.1.15&url=https://www.mobibrw.com/2020/27156&referred= Failed to load resource: the server responded with a status of 401 (Unauthorized) |
比较奇怪的是,如果此时用户登陆,那么反倒不返回任何错误了。
Android App上架说明
Android App应用商店上架说明
目录
一、App上架所需证件 2
1.1、软件著作权申请 2
1.2、特殊资质证明 2
1.3公司营业执照、税务登记证 2
二、Android主流应用市场上架说明 2
2.1、华为应用市场 2
2.2、小米应用商店 3
2.3、Oppo应用商店 3
2.4、Vivo应用商店 3
2.5、应用宝 3
2.6、Android其他应用市场 4
一、App上架所需证件
1.1、软件著作权申请
参考资料
http://www.ccopyright.com.cn/index.php?optionid=1079
1.2、特殊资质证明
新闻、直播、广播电视节目、股票、彩票、银行等,部分应用市场需要提交特殊运营资质证明材料,提交审核时候应用市场会有相应提示,根据各大应用市场要求填写即可。
1.3公司营业执照、税务登记证
当注册的开发者账号为企业账号时候,部分市场需要提供者两个证件。注册各大应用市场会有具体要求。
二、Android主流应用市场上架说明
2.1、华为应用市场
华为开发者账号登录注册地址
https://developer.huawei.com/consumer/cn/
华为市场App上架说明
https://developer.huawei.com/consumer/cn/doc
华为应用市场REST API自动化发布应用
https://developer.huawei.com/consumer/cn/doc/development/AppGallery-connect-Guides/agcapi-overview
2.2、小米应用商店
小米开发者账号注册
https://dev.mi.com/docs/appsmarket/distribution/account_register/
小米应用商店App上架说明
https://dev.mi.com/docs/appsmarket/distribution/app_submit/
小米应用市场REST API自动化发布应用
https://dev.mi.com/console/doc/detail?pId=33
https://dev.mi.com/console/doc/detail?pId=2352
2.3、Oppo应用商店
Oppo开发者账号注册登录地址 OPPO不支持个人发布应用
2.4、Vivo应用商店 VIVO 个人开发者无法注册账号
Vivo开发者账号注册登录地址
Vivo应用商店App上架说明
https://dev.vivo.com.cn/documentCenter/doc/52
VIVO应用市场REST API自动化发布应用
https://dev.vivo.com.cn/documentCenter/doc/327
VIVO提供了一个APK传包平台,貌似可以上传到其他应用商店,但是只有企业用户才能申请,具体怎么操作,暂时还不清楚。
2.5、应用宝
注册开发者账号
https://wiki.open.qq.com/index.php
创建应用
https://wiki.open.qq.com/wiki/%E5%88%9B%E5%BB%BA%E6%96%B0%E5%BA%94%E7%94%A8
应用宝暂时没办法通过 REST API自动化发布应用
注意:每个应用第一次在腾讯应用商店上架,通过审核后,都需要人工提交工单到应用宝,准备跟上架一样的材料,并且提供其他应用商店的上架链接,上架截图,然后等工单审批通过后,才能正常搜索到。
这个工单类型是 “应用宝外显级别调整申请”,如下图:
也就是说应用宝必须是其他应用商店已经上线的情况下,才能提供搜索下载安装功能。
2.6、魅族应用商店
应用商店上架说明:
https://open.flyme.cn/openNew/application.html
2.7、Android其他应用市场
360应用市场,百度应用市场、阿里应用分发平台、魅族应用商店。
参考链接
如何申请软件著作权?
一、关于文件准备
1、请自行浏览中国版权登记门户网 软件著作权登记唯一官方网站 中国版权保护中心网站 中国版权登记门户网 软件著作权登记唯一官方网站 中国版权保护中心网站 基本格式要求按照官网上的要求准备就好了,这个不赘述。
注意需要登陆账号之后,提供实名认证,包括身份证的正反面照片,手持身份证的照片,等待两三天实名审核通过之后才能提交申请。
关于费用问题,目前已经不收费了,这个注意。