通过 certbot 自动部署ssl证书
Let`s Encrypt 证书自动获取和部署
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx
sudo systemctl reload nginx
sudo certbot renew --dry-run
Let`s Encrypt 证书自动获取和部署
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx
sudo systemctl reload nginx
sudo certbot renew --dry-run
1、ArgGIS 按图层分别导出shp
原始没有坐标系,要手动配准:
投影坐标 4524 CGCS2000_3_Degree_GK_Zone_36
2、GeoServer
1)创建工作区
2)数据存储-添加新的数据存储-选择文件夹 Directory of spatial files (shapefiles)
3)图层-添加新的资源-选择要发布的文件-Native Bounding Box 从数据中计算
问题1:只能收到APP在线时推送的消息,离线消息收不到
问题2:一台设备一天只能收到2次消息推送
解决方法:
1、uniapp 要正确获取clientId,并且APP要获取通知权限
bindPushCid() {
var timer = setTimeout(function() {
plus.push.getClientInfoAsync(function(info) {
if (info.clientid) {
// 绑定到后台用户
updateGetuiClientId(info.clientid);
clearInterval(timer);
}
}, function(e) {
console.log(JSON.stringify(e));
})
}, 1000)
},
permissionPush(){
let platform = uni.getSystemInfoSync().platform
if (platform == 'android') {
/* 获取当前手机是否有通知权限 */
// let main = plus.android.runtimeMainActivity();
// let pkName = main.getPackageName();
// console.log("是否有通知权限pkName",pkName);
// let NotificationManagerCompat = plus.android.importClass("android.support.v4.app.NotificationManagerCompat");
// console.log("是否有通知权限NotificationManagerCompat",NotificationManagerCompat);
// let packageNames = NotificationManagerCompat.from(main);
var main = plus.android.runtimeMainActivity();
var NotificationManagerCompat = plus.android.importClass("androidx.core.app.NotificationManagerCompat");
let packageNames = NotificationManagerCompat.from(main);
let pkName = main.getPackageName();
if (!packageNames.areNotificationsEnabled()) { //手机没有开启通知的权限
uni.showModal({
title: '通知权限',
content: '通知权限暂未开启',
cancelText: '暂不开启',
confirmText: '前往开启',
success: function (res) {
if (res.confirm) {
let uid = main.getApplicationInfo().plusGetAttribute("uid");
let Intent = plus.android.importClass('android.content.Intent');
let Build = plus.android.importClass("android.os.Build");
let intent = '';
//android 8.0引导
if (Build.VERSION.SDK_INT >= 26) {
intent = new Intent('android.settings.APP_NOTIFICATION_SETTINGS');
intent.putExtra('android.provider.extra.APP_PACKAGE', pkName);
} else if (Build.VERSION.SDK_INT >= 21) { //android 5.0-7.0
intent = new Intent('android.settings.APP_NOTIFICATION_SETTINGS');
intent.putExtra("app_package", pkName);
intent.putExtra("app_uid", uid);
} else { //(<21)其他--跳转到该应用管理的详情页
let Settings = plus.android.importClass("android.provider.Settings");
let Uri = plus.android.importClass("android.net.Uri");
intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
let uri = Uri.fromParts("package", main.getPackageName(), null);
intent.setData(uri);
}
// 跳转到该应用的系统通知设置页
main.startActivity(intent);
} else if (res.cancel) {
console.log('用户点击取消');
}
},
fail: () => {},
complete: () => {}
});
}
}
}
2、华为开发者后台,申请“自分类权益”,以便解除通知条数限制,现在申请很简单,提交示例和截图,系统直接审核通过
3、厂家参数要注意细节
因为importance和category参数,调试很久
Map<String, Map<String, Object>> options = new HashMap<>();
Map<String, Object> oop = new HashMap<>();
oop.put("/message/android/notification/badge/class", "io.dcloud.PandoraEntry");
oop.put("/message/android/notification/badge/add_num", 1);
oop.put("/message/android/notification/importance", "NORMAL");
oop.put("/message/android/category", "WORK");
options.put("HW", oop);
ups.setOptions(options);
4、服务端完整代码
package com.budwk.app;
import com.getui.push.v2.sdk.ApiHelper;
import com.getui.push.v2.sdk.GtApiConfiguration;
import com.getui.push.v2.sdk.api.PushApi;
import com.getui.push.v2.sdk.common.ApiResult;
import com.getui.push.v2.sdk.dto.CommonEnum;
import com.getui.push.v2.sdk.dto.req.Audience;
import com.getui.push.v2.sdk.dto.req.AudienceDTO;
import com.getui.push.v2.sdk.dto.req.message.PushChannel;
import com.getui.push.v2.sdk.dto.req.message.PushDTO;
import com.getui.push.v2.sdk.dto.req.message.PushMessage;
import com.getui.push.v2.sdk.dto.req.message.android.AndroidDTO;
import com.getui.push.v2.sdk.dto.req.message.android.GTNotification;
import com.getui.push.v2.sdk.dto.req.message.android.ThirdNotification;
import com.getui.push.v2.sdk.dto.req.message.android.Ups;
import com.getui.push.v2.sdk.dto.req.message.harmony.HarmonyDTO;
import com.getui.push.v2.sdk.dto.req.message.harmony.HarmonyNotification;
import com.getui.push.v2.sdk.dto.res.TaskIdDTO;
import com.gexin.rp.sdk.base.IPushResult;
import com.gexin.rp.sdk.base.impl.AppMessage;
import com.gexin.rp.sdk.base.impl.ListMessage;
import com.gexin.rp.sdk.base.impl.SingleMessage;
import com.gexin.rp.sdk.base.impl.Target;
import com.gexin.rp.sdk.exceptions.RequestException;
import com.gexin.rp.sdk.http.IGtPush;
import com.gexin.rp.sdk.template.*;
import com.gexin.rp.sdk.template.style.Style0;
import lombok.extern.slf4j.Slf4j;
import org.nutz.json.Json;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
public class GeTuiUtil {
private static String appId;
private static String appKey;
private static String masterSecret;
private static String host;
private static Long OfflineExpireTime;
private static String logo;
private static boolean isRing;
private static boolean isVibrate;
private static boolean isClearable;
private static int transmissionType;
private static PushApi pushApi;
// 初始化个推的系统app参数
static {
appId = "";
appKey = "";
masterSecret = "";
host = "http://sdk.open.api.igexin.com/apiex.htm";
OfflineExpireTime = 259200000L;
logo = "icon.png";
isRing = true;
isVibrate = true;
isClearable =true;
transmissionType = 1;
System.setProperty("http.maxConnections", "200");
GtApiConfiguration apiConfiguration = new GtApiConfiguration();
//填写应用配置
apiConfiguration.setAppId(appId);
apiConfiguration.setAppKey(appKey);
apiConfiguration.setMasterSecret(masterSecret);
// 接口调用前缀,请查看文档: 接口调用规范 -> 接口前缀
apiConfiguration.setDomain("https://restapi.getui.com/v2/");
// 实例化ApiHelper对象,用于创建接口对象
ApiHelper apiHelper = ApiHelper.build(apiConfiguration);
// 创建对象,建议复用。目前有PushApi、StatisticApi、UserApi
pushApi = apiHelper.creatApi(PushApi.class);
}
public static Map<String, Object> sendSingleNew(String title, String content, String cid) {
PushDTO<Audience> pushDTO = new PushDTO<Audience>();
pushDTO.setRequestId(System.currentTimeMillis() + "");
buildPushMessage(title, content, pushDTO);
Audience audience = new Audience();
pushDTO.setAudience(audience);
audience.addCid(cid);
ApiResult<Map<String, Map<String, String>>> apiResult = pushApi.pushToSingleByCid(pushDTO);
log.info("sendSingleNew result :" + Json.toJson(apiResult));
Map<String, Object> result = new HashMap<>();
result.put("code", apiResult.getCode());
if (apiResult.isSuccess()) {
result.put("data", apiResult.getData());
} else {
result.put("msg", apiResult.getMsg());
}
return result;
}
public static void main(String[] args) {
String title = "你有新的待办任务";
String content = "任务内容:测" + System.currentTimeMillis();
String cid = "77336b3d6d136da0cf56e288a7462949";
Map<String, Object> stringObjectMap = sendSingleNew(title, content, cid);
// List<String> cids = new ArrayList<>();
// cids.add(cid);
// Map<String, Object> stringObjectMap = sendBatchNew(title, content, cids);
// Map<String, Object> stringObjectMap = sendAllClientNew(title, content);
//
}
private static PushMessage buildPushMessage(String title, String content, PushDTO pushDTO) {
PushMessage pushMessage = new PushMessage();
pushDTO.setPushMessage(pushMessage);
GTNotification notification = new GTNotification();
pushMessage.setNotification(notification);
notification.setTitle(title);
notification.setBody(content);
notification.setBadgeAddNum("1");
notification.setClickType(CommonEnum.ClickTypeEnum.TYPE_STARTAPP.type);
notification.setSlotType("2");
notification.setCategory("CATEGORY_REMINDER");
PushChannel pushChannel = new PushChannel();
pushDTO.setPushChannel(pushChannel);
AndroidDTO androidDTO = new AndroidDTO();
pushChannel.setAndroid(androidDTO);
Ups ups = new Ups();
androidDTO.setUps(ups);
ThirdNotification thirdNotification = new ThirdNotification();
ups.setNotification(thirdNotification);
thirdNotification.setTitle(title);
thirdNotification.setBody(content);
thirdNotification.setClickType(CommonEnum.ClickTypeEnum.TYPE_STARTAPP.type);
Map<String, Map<String, Object>> options = new HashMap<>();
Map<String, Object> oop = new HashMap<>();
oop.put("/message/android/notification/badge/class", "io.dcloud.PandoraEntry");
oop.put("/message/android/notification/badge/add_num", 1);
oop.put("/message/android/notification/importance", "NORMAL");
oop.put("/message/android/category", "WORK");
options.put("HW", oop);
ups.setOptions(options);
HarmonyDTO harmonyDTO = new HarmonyDTO();
pushChannel.setHarmony(harmonyDTO);
HarmonyNotification harmonyNotification = new HarmonyNotification();
harmonyDTO.setNotification(harmonyNotification);
harmonyNotification.setTitle(title);
harmonyNotification.setBody(content);
harmonyNotification.setCategory("CATEGORY_REMINDER");
harmonyNotification.setClickType(CommonEnum.ClickTypeEnum.TYPE_STARTAPP.type);
return pushMessage;
}
private static Map<String, Object> send(AbstractTemplate template, List<String> cids) {
if (cids == null || cids.size() == 0) {
// 给appId下所有人发
return sendAllClient(template);
}
if (cids.size() == 1) {
// 单发
Map<String, Object> result = sendSingle(template, cids.get(0));
return result;
} else {
// 群发
Map<String, Object> result = sendBatch(template, cids);
return result;
}
}
private static Map<String, Object> sendNew(String title, String content, List<String> cids) {
log.info("进入推送信息:" + title);
if (cids == null || cids.size() == 0) {
// 给appId下所有人发
Map<String, Object> stringObjectMap = sendAllClientNew(title, content);
log.info("APP群推发送返回结果:" + Json.toJson(stringObjectMap));
return stringObjectMap;
}
if (cids.size() == 1) {
// 单发
Map<String, Object> result = sendSingleNew(title, content, cids.get(0));
log.info("单推发送返回结果:" + Json.toJson(result));
return result;
} else {
// 群发
Map<String, Object> result = sendBatchNew(title, content, cids);
log.info("群推发送返回结果:" + Json.toJson(result));
return result;
}
}
/**
* @Description 发送通知模板消息
* @Author daitao
* @version 1.0
* @Date 2019/4/24 17:57
* @Param title:消息标题
* @Param content:消息内容
* @Param cids: 1、null或size==0:表示给appId下所有人发
* 2、size为1:单发
* 3、size大于1:群发
* @Return map类型, 可能为null,例如:"{result=ok, contentId=OSL-0424_y2LxEeM6hvA0yhTqa77qw4,
* details={"c85fa1218fe4c54652a77bef22726fb0":"TokenMD5Error",
* "a5800ff27659a5258b8ba86e4e1d7c87":"successed_online"
* }
* }"
* result==ok,发送成功,details里面是每个cid对应的发送结果,包含successed表示成功,其他失败
*/
public static Map<String, Object> sendNotification(String title, String content, List<String> cids) {
NotificationTemplate template = buildNotificationTemplate(title, content);
return send(template, cids);
}
public static Map<String, Object> sendNotificationNew(String title, String content, List<String> cids) {
return sendNew(title, content, cids);
}
/**
* @param
* @param template
* @param msgItem
* @return
* @throws
* @author daitao
* @version 1.0
* @description 个推单发
* @date 2019/4/12
**/
private static Map<String, Object> sendSingle(AbstractTemplate template, String cid) {
IGtPush push = new IGtPush(host, appKey, masterSecret);
SingleMessage message = new SingleMessage();
message.setOffline(true);
// 离线有效时间,单位为毫秒,可选
message.setOfflineExpireTime(OfflineExpireTime);
message.setData(template);
// 可选,1为wifi,0为不限制网络环境。根据手机处于的网络情况,决定是否下发
message.setPushNetWorkType(0);
Target target = new Target();
target.setAppId(appId);
target.setClientId(cid);
IPushResult ret;
try {
ret = push.pushMessageToSingle(message, target);
} catch (RequestException e) {
e.printStackTrace();
ret = push.pushMessageToSingle(message, target, e.getRequestId());
}
return ret.getResponse();
}
/**
* @param
* @param template
* @param msgItem
* @return
* @throws
* @author daitao
* @version 1.0
* @description 个推单发
* @date 2019/4/12
**/
private static Map<String, Object> sendBatch(AbstractTemplate template, List<String> cids) {
// 配置返回每个用户返回用户状态,可选
System.setProperty("gexin_pushList_needDetails", "true");
IGtPush push = new IGtPush(host, appKey, masterSecret);
// 通知透传模板
ListMessage message = new ListMessage();
message.setData(template);
// 设置消息离线,并设置离线时间
message.setOffline(true);
// 离线有效时间,单位为毫秒,可选
message.setOfflineExpireTime(OfflineExpireTime);
// taskId用于在推送时去查找对应的message
String taskId = push.getContentId(message);
List<Target> targets = handleTargets(cids);
IPushResult ret = push.pushMessageToList(taskId, targets);
return ret.getResponse();
}
private static Map<String, Object> sendBatchNew(String title, String content, List<String> cids) {
Map<String, Object> result = new HashMap<>();
PushDTO<String> pushDTO = new PushDTO<String>();
pushDTO.setRequestId(System.currentTimeMillis() + "");
buildPushMessage(title, content, pushDTO);
ApiResult<TaskIdDTO> createResult = pushApi.createMsg(pushDTO);
log.info("sendBatchNew createResult :" + Json.toJson(createResult));
result.put("code", createResult.getCode());
if (createResult.isSuccess()) {
result.put("data", createResult.getData());
TaskIdDTO taskIdDTO = createResult.getData();
AudienceDTO pushBatchDTO = new AudienceDTO();
pushBatchDTO.setTaskid(taskIdDTO.getTaskId());
Audience audience = new Audience();
audience.setCid(cids);
pushBatchDTO.setAudience(audience);
ApiResult<Map<String, Map<String, String>>> apiResult = pushApi.pushListByCid(pushBatchDTO);
log.info("sendBatchNew apiResult :" + Json.toJson(apiResult));
result.put("code", apiResult.getCode());
if (apiResult.isSuccess()) {
result.put("data", apiResult.getData());
} else {
result.put("msg", apiResult.getMsg());
}
} else {
result.put("msg", createResult.getMsg());
}
return result;
}
private static List<Target> handleTargets(List<String> cids) {
List<Target> targets = new ArrayList<>();
for (String cid : cids) {
Target target = new Target();
target.setAppId(appId);
target.setClientId(cid);
targets.add(target);
}
return targets;
}
private static NotificationTemplate buildNotificationTemplate(String title, String content) {
NotificationTemplate template = new NotificationTemplate();
// 设置APPID与APPKEY
template.setAppId(appId);
template.setAppkey(appKey);
template.setTransmissionType(transmissionType);
Style0 style = new Style0();
// 设置通知栏标题与内容
style.setTitle(title);
style.setText(content);
// 配置通知栏图标
style.setLogo(logo);
// 配置通知栏网络图标
style.setLogoUrl("");
// 设置通知是否响铃,震动,或者可清除
style.setRing(isRing);
style.setVibrate(isVibrate);
style.setClearable(isClearable);
template.setStyle(style);
return template;
}
/**
* @Description
* @Author daitao
* @version 1.0
* @Date 2019/4/24 17:57
* @Param cids: 1、null或size==0:表示给appId下所有人发
* 2、size为1:单发
* 3、size大于1:群发
* @Return
* @Exception
*/
public static Map<String, Object> sendLinkTemplate(String title, String content, String openUrl, List<String> cids) {
LinkTemplate template = buildLinkTemplate(title, content, openUrl);
return send(template, cids);
}
private static LinkTemplate buildLinkTemplate(String title, String content, String openUrl) {
LinkTemplate template = new LinkTemplate();
// 设置APPID与APPKEY
template.setAppId(appId);
template.setAppkey(appKey);
Style0 style = new Style0();
// 设置通知栏标题与内容
style.setTitle(title);
style.setText(content);
// 配置通知栏图标
style.setLogo(logo);
// 配置通知栏网络图标
style.setLogoUrl("");
// 设置通知是否响铃,震动,或者可清除
style.setRing(isRing);
style.setVibrate(isVibrate);
style.setClearable(isClearable);
template.setStyle(style);
// 设置打开的网址地址
template.setUrl(openUrl);
return template;
}
/**
* @Description
* @Author daitao
* @version 1.0
* @Date 2019/4/24 17:57
* @Param cids: 1、null:表示给appId下所有人发
* 2、size为1:单发
* 3、size大于1:群发
* @Return
* @Exception
*/
public static Map<String, Object> sendNotyPopLoadTemplate(String title, String content,
String popTitle, String popContent, String downloadTitle, String downloadIcon, String downloadUrl, List<String> cids) {
NotyPopLoadTemplate template = buildNotyPopLoadTemplate(title, content, popTitle, popContent, downloadTitle, downloadIcon, downloadUrl);
return send(template, cids);
}
/**
* @param title 消息标题
* @param content 消息内容
* @param popTitle 弹框标题
* @param popContent 弹框内容
* @param downloadTitle 下载标题
* @param downloadIcon 下载图标
* @param downloadUrl 下载的url资源地址
* @return
*/
private static NotyPopLoadTemplate buildNotyPopLoadTemplate(String title, String content,
String popTitle, String popContent, String downloadTitle, String downloadIcon, String downloadUrl
) {
NotyPopLoadTemplate template = new NotyPopLoadTemplate();
// 设置APPID与APPKEY
template.setAppId(appId);
template.setAppkey(appKey);
Style0 style = new Style0();
// 设置通知栏标题与内容
style.setTitle(title);
style.setText(content);
// 配置通知栏图标
style.setLogo(logo);
// 配置通知栏网络图标
style.setLogoUrl("");
// 设置通知是否响铃,震动,或者可清除
style.setRing(isRing);
style.setVibrate(isVibrate);
style.setClearable(isClearable);
template.setStyle(style);
// 设置弹框标题与内容
template.setPopTitle(popTitle);
template.setPopContent(popContent);
// 设置弹框显示的图片
template.setPopImage("");
template.setPopButton1("下载");
template.setPopButton2("取消");
// 设置下载标题
template.setLoadTitle(downloadTitle);
template.setLoadIcon(downloadIcon);
//设置下载地址
template.setLoadUrl(downloadUrl);
return template;
}
/**
* @Description
* @Author daitao
* @version 1.0
* @Date 2019/4/24 17:57
* @Param cids: 1、null:表示给appId下所有人发
* 2、size为1:单发
* 3、size大于1:群发
* @Return
* @Exception
*/
public static Map<String, Object> sendTransmissionTemplate(String title, String content, List<String> cids) {
TransmissionTemplate template = buildTransmissionTemplate(title, content);
return send(template, cids);
}
/**
* 安卓推送透传消息模板
*
* @param title
* @param content
* @return
*/
private static TransmissionTemplate buildTransmissionTemplate(String title, String content) {
TransmissionTemplate template = new TransmissionTemplate();
template.setAppId(appId);
template.setAppkey(appKey);
// 透传消息设置,1为强制启动应用,客户端接收到消息后就会立即启动应用;2为等待应用启动
template.setTransmissionType(transmissionType);
template.setTransmissionContent(content);
return template;
}
/**
* @param oldTaskId 指定需要撤回消息对应的taskId
* @param force 客户端没有找到对应的taskid,是否把对应appid下所有的通知都撤回
* @Description
* @Author daitao
* @version 1.0
* @Date 2019/4/24 17:57
* @Param cids: 1、null:表示给appId下所有人发
* 2、size为1:单发
* 3、size大于1:群发
* @Return
* @Exception
*/
public static Map<String, Object> sendRevokeTemplate(String oldTaskId, boolean force, List<String> cids) {
RevokeTemplate template = getRevokeTemplate(oldTaskId, force);
return send(template, cids);
}
/**
* 获取撤回模板
*
* @param oldTaskId 指定需要撤回消息对应的taskId
* @param force 客户端没有找到对应的taskid,是否把对应appid下所有的通知都撤回
* @return
*/
private static RevokeTemplate getRevokeTemplate(String oldTaskId, boolean force) {
RevokeTemplate template = new RevokeTemplate();
template.setAppId(appId);// 应用appid
template.setAppkey(appKey);// 应用appkey
template.setOldTaskId(oldTaskId);
return template;
}
/**
* 发个应用的所有客户端
*
* @param template
* @return
*/
private static Map<String, Object> sendAllClient(AbstractTemplate template) {
IGtPush push = new IGtPush(host, appKey, masterSecret);
// 定义"点击链接打开通知模板",并设置标题、内容、链接
List<String> appIds = new ArrayList<String>();
appIds.add(appId);
// 定义"AppMessage"类型消息对象,设置消息内容模板、发送的目标App列表、是否支持离线发送、以及离线消息有效期(单位毫秒)
AppMessage message = new AppMessage();
message.setData(template);
message.setAppIdList(appIds);
message.setOffline(true);
message.setOfflineExpireTime(OfflineExpireTime);
IPushResult ret = push.pushMessageToApp(message);
return ret.getResponse();
}
private static Map<String, Object> sendAllClientNew(String title, String content) {
PushDTO<String> pushDTO = new PushDTO<String>();
pushDTO.setRequestId(System.currentTimeMillis() + "");
pushDTO.setAudience("all");
buildPushMessage(title, content, pushDTO);
ApiResult<TaskIdDTO> apiResult = pushApi.pushAll(pushDTO);
Map<String, Object> result = new HashMap<>();
log.info("sendAllClientNew result :" + Json.toJson(apiResult));
result.put("code", apiResult.getCode());
if (apiResult.isSuccess()) {
result.put("data", apiResult.getData());
} else {
result.put("msg", apiResult.getMsg());
}
return result;
}
}
pom.xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<!--个推-->
<dependency>
<groupId>com.gexin.platform</groupId>
<artifactId>gexin-rp-sdk-http</artifactId>
<version>4.1.0.1</version>
</dependency>
<dependency>
<groupId>com.gexin.platform</groupId>
<artifactId>gexin-rp-sdk-base</artifactId>
<version>4.0.0.26</version>
</dependency>
<dependency>
<groupId>com.gexin.platform</groupId>
<artifactId>gexin-rp-sdk-template</artifactId>
<version>4.0.0.20</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.gexin.platform/gexin-rp-fastjson -->
<!-- https://mvnrepository.com/artifact/com.getui.push/restful-sdk -->
<dependency>
<groupId>com.getui.push</groupId>
<artifactId>restful-sdk</artifactId>
<version>1.0.6.0</version>
</dependency>
<dependency>
<groupId>com.gexin.platform</groupId>
<artifactId>gexin-rp-fastjson</artifactId>
<version>1.0.0.1</version>
</dependency>
仓库
<repository>
<id>getui</id>
<url>http://mvn.gt.getui.com/nexus/content/repositories/releases/</url>
</repository>
以下以Nutz代码为例:
Header header = Header.create();
header.addv("Content-Type", "application/json");
header.addv("AppCode", appcode);
Request request = Request.create(url, Request.METHOD.POST);
NutMap body = NutMap.NEW();
body.put("kssj", kssj);
body.put("jssj", jssj);
request.setHeader(header);
request.setData(Json.toJson(body));
Sender sender = Sender.create(request).setTimeout(30 * 1000);
if (url.startsWith("https")) {
try {
SSLContext sslcontext = this.createIgnoreVerifySSL();
sender.setSSLSocketFactory(sslcontext.getSocketFactory());
sender.setHostnameVerifier((urlHostName, session) -> true);
} catch (Exception e) {
e.printStackTrace();
}
}
Response response = sender.send();
if (response.isOK()) {
//todo
}
private static class TrustAllManager
implements X509TrustManager {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkServerTrusted(X509Certificate[] certs,
String authType) {
}
public void checkClientTrusted(X509Certificate[] certs,
String authType) {
}
}
public SSLContext createIgnoreVerifySSL() throws NoSuchAlgorithmException, KeyManagementException {
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, new TrustManager[]{new CityLifelineServer.TrustAllManager()}, null);
return sc;
}
ALTER USER 'root'@'%' IDENTIFIED BY 'pwd' PASSWORD EXPIRE NEVER;
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'pwd';
FLUSH PRIVILEGES;
BUDIOT 是一个开源的、企业级的物联网平台,它集成了设备管理、协议解析、消息订阅、场景联动等一系列物联网核心能力,支持以平台适配设备的方式连接海量设备,支持在线下发指令实现远程控制,支持扩展水电气等各类计费业务场景。
本平台是在千万级设备实时计费物联网平台经验基础上,在不损失性能的前提下进行功能删减、结构优化而来,小而美,同时又具备灵活的扩展性。
在线演示地址: https://demo.budiot.com 用户名: superadmin 密码: 1
基于自研 Java 微服务框架 https://budwk.com
budiot-access/budiot-access-gateway
设备网关,用于设备协议和 network 组件budiot-access/budiot-access-processor
设备数据上报业务处理模块budiot-server
WEB 服务 API ,定时任务等budiot-access/budiot-access-network
网络组件,支持 TCP/MQTT/UDP/HTTP 等budiot-access/budiot-access-protocol
设备协议开发包,内含 demo 示例budiot-access/budiot-access-storage
设备数据存储,可扩展时序数据库等budiot-vue-admin
Vue3 + Element-Plus默认采用 MongoDB 7 的时序集合,可根据项目规模需要,扩展为 TDEngine 等时序数据库
java.util.concurrent.ExecutionException: java.lang.IllegalArgumentException: Comparison method violates its general contract!
错误代码: children.sort((o1, o2) -> (int) (o2.getLong("createdAt", 0) - o1.getLong("createdAt", 0)));
修复后: children.sort((o1, o2) -> Long.compare(o2.getLong("createdAt", 0), o1.getLong("createdAt", 0)));
排序器 children.sort((o1, o2) -> (int) (o2.getLong("createdAt", 0) - o1.getLong("createdAt", 0)));
存在一个潜在的问题,即当时间戳的差值超出 int
的范围时,强制转换为 int
会导致数据溢出,从而导致比较结果不正确。
一个解决方法是使用 Long.compare
方法进行比较,而不是手动进行减法运算。Long.compare
方法确保了比较的对称性和一致性。
Mongod 的服务模型是每个网络连接由一个单独的线程来处理,每个线程配置了1MB 的栈空间,当网络连接数太多时,过多的线程会导致上下文切换开销变大,同时内存开销也会上涨。
&maxPoolSize=xx
在ConnectionURI上追加上去即可,否则默认每个客户端就是高处100来个,平白的浪费资源启动需指定配置文件(配置数据库用户和密码,用户不可为默认用户)
python3 odoo-bin -c debian/odoo.conf -i base -d odoo
配置文件
[options]
; This is the password that allows database operations:
; admin_passwd = admin
db_host = 127.0.0.1
db_port = 5432
db_user = odoo
db_password = 1234567890
addons_path = /data/odoo/addons
default_productivity_apps = True
需安装依赖
pip3 install -r requirements.txt
su postgres
pgsql
\l 列出数据库
\du 列出用户
\q 退出窗口
su – postgres
pgsql
select usename,passwd from pg_shadow;
ALTER USER demo with password ‘demo12345678’;
或创建用户
CREATE USER demo WITH PASSWORD ‘demo12345678’;
赋予权限
ALTER USER demo WITH SUPERUSER;
ALTER USER demo WITH CreateDB;
创建数据库
CREATE DATABASEdemo
OWNERdemo
;
1、文档说明要求 python 3版本,但没有说必须是 3.6 版本,其他版本报错找不到 libpython3.6m.so.1.0;
2、使用 python 3.6 得安装 requests 组件,文档没有说明;
3、部署时必须指定执行的 sh 脚本文件;
Dcokerfile 文件内容如下:
FROM python:3.6-slim
WROKDIR /app
COPY . /app
RUN python -m pip install --upgrade pip
RUN pip install requests
RUN pip install pycryptodome
EXPOSE 8080
ps:搞过微信开发的都知道,微信的开发文档到底是多么多么的……
职位描述:
1、负责与开发组长沟通业务需求,与后端开发人员对接后端API、开发前端页面;
2、负责修改公司现有软件项目BUG,并对前端功能的持续优化改进;
3、负责公司现有前端框架和组件的维护、改进;
职位要求:
1、2年以上前端开发经验;
2、熟悉Web前端开发基础技能(HTML/CSS/JavaScript);
3、熟悉Vue2、Vue3、ElementUI、Element-Plus等前端开发框架;
4、对前端工程化与模块化开发有一定了解,有webpack/vite/npm实践经验;
5、有GIS开发经验(电子围栏、轨迹、图层等)、数据大屏开发经验者优先;
6、对前端开发有浓厚兴趣,能主动学习;
7、有良好的编码习惯,重视代码质量;
8、善于沟通,工作积极,有责任心,善于协作与分享;
岗位职责:
1、负责Java软件项目的全流程测试,包括制定测试计划、编写测试方案和测试用例等;
2、负责Java软件项目的功能、性能等方面的测试工作,执行测试用例,提交BUG,并进行BUG跟踪和回归测试,直到BUG解决;
3、负责Java软件项目的日常更新维护,包括Docker容器化部署等环境;
4、负责收集、跟进客户的使用问题,直到问题被有效解决;
任职资格:
1、本科及以上学历,计算机或相关专业,3年以上软件测试经验;
2、熟悉软件研发、测试流程,了解过程标准和规范,能主动在关键过程节点推动任务执行;
3、熟悉软件测试方法和软件工程知识,流程意识强,具备有效发现问题和解决问题的能力;
4、熟悉Docker部署,熟练掌握Linux服务器的配置和管理;
5、了解常见开源软件的集群化部署、维护和使用,例如RabbitMQ,MongoDB,Redis,MySQL/MariaDB、ElasticSearch等
6、具有一定的文档编写能力,如部署文档、测试方案、测试用例等;
<style scoped>
::v-deep .el-table {
display: flex;
flex-direction: column;
}
::v-deep .el-table__body-wrapper {
order: 1;
}
::v-deep .el-table__fixed-body-wrapper {
top: 92px !important;
}
::v-deep .el-table__fixed-footer-wrapper {
z-index: 0;
top: 45px;
}
</style>
<template>
<div class="data-list">
<transition name="el-fade-in">
<div v-show="selectRows.length > 0" class="el-selection-bar">
选择了<span class="el-selection-bar__text">{{ selectRows.length }}</span
>条数据
<a class="el-selection-bar__clear" @click="clearSelection">清空</a>
</div>
</transition>
<div v-loading="isRequestIng" class="data-list__table" :element-loading-text="loadingTxt">
<span v-if="summary">总条数:{{ rows.length }}</span>
<el-table
v-bind="$attrs"
ref="tabList"
:data="rows"
v-on="$listeners"
:border="true"
:span-method="handleRowSpanMethod"
:show-summary="summary"
sum-text=" 汇 总 "
>
<slot />
<pro-empty slot="empty" />
</el-table>
</div>
<div v-if="!summary && (pageData.totalCount > pageData.pageSize || pageData.pageSize > 10)" class="data-list__pager">
<el-pagination
:current-page="pageData.pageNo"
:page-size="pageData.pageSize"
:total="pageData.totalCount"
background
:page-sizes="pageSizes"
layout="total, ->, prev, pager, next, sizes, jumper"
@current-change="doChangePage"
@size-change="doSizeChange"
/>
</div>
</div>
</template>
<script>
import { forIn, findIndex, cloneDeep, remove, uniqBy, concat, isArray, first } from "lodash-es"
import { f } from 'vue-marquee-component'
export default {
name: "PlusTableList",
props: {
server: {
type: String,
require: true,
default: ""
},
methods: {
type: String,
default: "post"
},
lazy: {
type: Boolean,
default: false
},
data: {
type: Object,
default: () => {}
},
dataFilter: {
type: Function,
default: data => data
},
loadingTxt: {
type: String,
default: "数据加载中..."
},
paramClear: {
type: Boolean,
default: false
},
selectRows: {
type: Array,
default: () => []
},
selectable: {
type: Function,
default: () => true
},
rowKey: {
type: String,
default: "id"
},
selection: {
type: Boolean,
default: true
},
pageSizes: {
type: Array,
default: () => [10, 20, 30, 50]
},
// 合并行(第一列)
spanName0: {
type: String,
default: null
},
// 合并行(第二列)
spanName1: {
type: String,
default: null
},
// 是否汇总数据
summary: {
type: Boolean,
default: false
}
},
data() {
return {
pageData: {
pageNo: 1,
pageSize: 10,
totalCount: 0
},
rows: [],
isRequestIng: false
}
},
watch: {
pageSizes: {
handler: function (val) {
if (isArray(val)) {
this.pageData.pageSize = first(val)
}
},
immediate: true
}
},
mounted() {
if (!this.lazy) {
this.getList()
}
},
methods: {
// 合并相同值的行
handleRowSpanMethod({ row, column, rowIndex, columnIndex }) {
if (columnIndex === 0) {
if (!this.spanName0) return
if (rowIndex > 0 && row[this.spanName0] === this.rows[rowIndex - 1][this.spanName0]) {
return {
rowspan: 0,
colspan: 0
}
} else {
let count = 1
for (let i = rowIndex + 1; i < this.rows.length; i++) {
if (row[this.spanName0] === this.rows[i][this.spanName0]) {
count++
} else {
break
}
}
if(count>1){
return {
rowspan: count,
colspan: 1
}
}
}
}
if (columnIndex === 1) {
if (!this.spanName1) return
// 第一列值相同,且第二列值相同的的情况下合并
if (rowIndex > 0 && row[this.spanName0] === this.rows[rowIndex-1][this.spanName0] && row[this.spanName1] === this.rows[rowIndex - 1][this.spanName1]) {
return {
rowspan: 0,
colspan: 0
}
} else {
let count = 1
for (let i = rowIndex + 1; i < this.rows.length; i++) {
// 第一列值相同,且第二列值相同的的情况下合并
if (row[this.spanName0] === this.rows[i][this.spanName0] && row[this.spanName1] === this.rows[i][this.spanName1]) {
count++
} else {
break
}
}
if(count>1){
return {
rowspan: count,
colspan: 1
}
}
}
}
},
// 页码变动事件
doChangePage(val) {
this.pageData.pageNo = val
this.getList()
},
// 页大小变动事件
doSizeChange(val) {
this.pageData.pageSize = val
this.pageData.pageNo = 1
this.getList()
},
getList() {
const { totalCount, ...pager } = this.pageData
const params = Object.assign({}, this.data, pager)
if (this.paramClear) {
forIn(params, (value, key) => {
if (value === "") delete params[key]
})
}
this.isRequestIng = true
this.$get(params)
.then(({ data }) => {
this.rows = this.dataFilter(data.list || [])
this.pageData.totalCount = data.totalCount
this.$emit("updateTotal", data.totalCount)
this.isRequestIng = false
this.$nextTick(() => {
this.handlePageUpdate()
})
})
.catch(error => {
this.isRequestIng = false
})
},
handlePageUpdate() {
const list = this.rows
list.forEach(row => {
if (
findIndex(this.selectRows, el => {
return el[this.rowKey] === row[this.rowKey]
}) !== -1
) {
this.$refs.tabList.toggleRowSelection(row, true)
}
})
},
handleSelectionChange(val) {
const selectRows = cloneDeep(this.selectRows)
this.$nextTick(() => {
const list = this.rows.map(el => el[this.rowKey])
remove(selectRows, el => {
return list.includes(el[this.rowKey])
})
this.$emit(
"update:selectRows",
uniqBy(concat(selectRows, val), el => el[this.rowKey])
)
})
},
$get(data) {
if (this.methods === "get") {
return this.$axios.get(this.server, {
params: data
})
} else {
return this.$axios.post(this.server, data)
}
},
reset() {
this.pageData.pageNo = 1
this.pageData.totalCount = 0
this.rows = []
this.clearSelection()
},
async clearSelection() {
this.$refs.tabList.clearSelection()
await this.$nextTick()
this.$emit("update:selectRows", [])
},
query() {
this.reset()
this.getList()
}
}
}
</script>
<template>
<div class="data-list">
<transition name="el-fade-in">
<div v-show="selectRows.length > 0" class="el-selection-bar">
选择了<span class="el-selection-bar__text">{{ selectRows.length }}</span
>条数据
<a class="el-selection-bar__clear" @click="clearSelection">清空</a>
</div>
</transition>
<div v-loading="isRequestIng" class="data-list__table" :element-loading-text="loadingTxt">
<el-table
v-bind="$attrs"
ref="tabList"
:data="rows"
v-on="$listeners"
:border="true"
:span-method="handleRowSpanMethod"
>
<slot />
<pro-empty slot="empty" />
</el-table>
</div>
<div v-if="pageData.totalCount > pageData.pageSize || pageData.pageSize > 10" class="data-list__pager">
<el-pagination
:current-page="pageData.pageNo"
:page-size="pageData.pageSize"
:total="pageData.totalCount"
background
:page-sizes="pageSizes"
layout="total, ->, prev, pager, next, sizes, jumper"
@current-change="doChangePage"
@size-change="doSizeChange"
/>
</div>
</div>
</template>
<script>
import { forIn, findIndex, cloneDeep, remove, uniqBy, concat, isArray, first } from "lodash-es"
export default {
name: "PlusTableList",
props: {
server: {
type: String,
require: true,
default: ""
},
methods: {
type: String,
default: "post"
},
lazy: {
type: Boolean,
default: false
},
data: {
type: Object,
default: () => {}
},
dataFilter: {
type: Function,
default: data => data
},
loadingTxt: {
type: String,
default: "数据加载中..."
},
paramClear: {
type: Boolean,
default: false
},
selectRows: {
type: Array,
default: () => []
},
selectable: {
type: Function,
default: () => true
},
rowKey: {
type: String,
default: "id"
},
selection: {
type: Boolean,
default: true
},
pageSizes: {
type: Array,
default: () => [10, 20, 30, 50]
},
spanName0: {
type: String,
default: null
},
spanName1: {
type: String,
default: null
}
},
data() {
return {
pageData: {
pageNo: 1,
pageSize: 10,
totalCount: 0
},
rows: [],
isRequestIng: false
}
},
watch: {
pageSizes: {
handler: function (val) {
if (isArray(val)) {
this.pageData.pageSize = first(val)
}
},
immediate: true
}
},
mounted() {
if (!this.lazy) {
this.getList()
}
},
methods: {
// 合并相同值的行
handleRowSpanMethod({ row, column, rowIndex, columnIndex }) {
if (columnIndex === 0) {
if (!this.spanName0) return
if (rowIndex > 0 && row[this.spanName0] === this.rows[rowIndex - 1][this.spanName0]) {
return {
rowspan: 0,
colspan: 0
}
} else {
let count = 1
for (let i = rowIndex + 1; i < this.rows.length; i++) {
if (row[this.spanName0] === this.rows[i][this.spanName0]) {
count++
} else {
break
}
}
if(count>1){
return {
rowspan: count,
colspan: 1
}
}
}
}
if (columnIndex === 1) {
if (!this.spanName1) return
// 第一列值相同,且第二列值相同的的情况下合并
if (rowIndex > 0 && row[this.spanName0] === this.rows[rowIndex-1][this.spanName0] && row[this.spanName1] === this.rows[rowIndex - 1][this.spanName1]) {
return {
rowspan: 0,
colspan: 0
}
} else {
let count = 1
for (let i = rowIndex + 1; i < this.rows.length; i++) {
// 第一列值相同,且第二列值相同的的情况下合并
if (row[this.spanName0] === this.rows[i][this.spanName0] && row[this.spanName1] === this.rows[i][this.spanName1]) {
count++
} else {
break
}
}
if(count>1){
return {
rowspan: count,
colspan: 1
}
}
}
}
},
// 页码变动事件
doChangePage(val) {
this.pageData.pageNo = val
this.getList()
},
// 页大小变动事件
doSizeChange(val) {
this.pageData.pageSize = val
this.pageData.pageNo = 1
this.getList()
},
getList() {
const { totalCount, ...pager } = this.pageData
const params = Object.assign({}, this.data, pager)
if (this.paramClear) {
forIn(params, (value, key) => {
if (value === "") delete params[key]
})
}
this.isRequestIng = true
this.$get(params)
.then(({ data }) => {
this.rows = this.dataFilter(data.list || [])
this.pageData.totalCount = data.totalCount
this.$emit("updateTotal", data.totalCount)
this.isRequestIng = false
this.$nextTick(() => {
this.handlePageUpdate()
})
})
.catch(error => {
this.isRequestIng = false
})
},
handlePageUpdate() {
const list = this.rows
list.forEach(row => {
if (
findIndex(this.selectRows, el => {
return el[this.rowKey] === row[this.rowKey]
}) !== -1
) {
this.$refs.tabList.toggleRowSelection(row, true)
}
})
},
handleSelectionChange(val) {
const selectRows = cloneDeep(this.selectRows)
this.$nextTick(() => {
const list = this.rows.map(el => el[this.rowKey])
remove(selectRows, el => {
return list.includes(el[this.rowKey])
})
this.$emit(
"update:selectRows",
uniqBy(concat(selectRows, val), el => el[this.rowKey])
)
})
},
$get(data) {
if (this.methods === "get") {
return this.$axios.get(this.server, {
params: data
})
} else {
return this.$axios.post(this.server, data)
}
},
reset() {
this.pageData.pageNo = 1
this.pageData.totalCount = 0
this.rows = []
this.clearSelection()
},
async clearSelection() {
this.$refs.tabList.clearSelection()
await this.$nextTick()
this.$emit("update:selectRows", [])
},
query() {
this.reset()
this.getList()
}
}
}
</script>
@IocBean
@Slf4j
public class CimApiServer {
@Inject
private RedisService redisService;
private String redis_key = "cim:accessToken";
@Inject
@Reference(check = false)
private ISysConfigProvider sysConfigProvider;
public String getAccessToken() {
String token = redisService.get(redis_key);
if (Strings.isBlank(token)) {
token = this.getHttpToken();
redisService.setex(redis_key, 3600 * 24 - 100, token);
}
return token;
}
private String getHttpToken() {
String CIM_GIS_APPID = sysConfigProvider.getString("COMMON", "CIM_GIS_APPID");
String CIM_GIS_HTTP_BASE = sysConfigProvider.getString("COMMON", "CIM_GIS_HTTP_BASE");
String CIM_GIS_APPKEY = sysConfigProvider.getString("COMMON", "CIM_GIS_APPKEY");
String CIM_GIS_APPSECRET = sysConfigProvider.getString("COMMON", "CIM_GIS_APPSECRET");
Map<String, Object> params = new HashMap<>();
params.put("apiKey", CIM_GIS_APPKEY);
params.put("secret", CIM_GIS_APPSECRET);
Header header = Header.create();
header.addv("Content-Type", "application/json");
Request request = Request.create(CIM_GIS_HTTP_BASE + "/auth/getAccessToken", Request.METHOD.POST);
request.setHeader(header);
request.setData(Json.toJson(params));
Sender sender = Sender.create(request).setTimeout(20 * 1000);
if (CIM_GIS_HTTP_BASE.startsWith("https")) {
try {
SSLContext sslcontext = createIgnoreVerifySSL();
sender.setSSLSocketFactory(sslcontext.getSocketFactory());
sender.setHostnameVerifier((urlHostName, session) -> true);
} catch (Exception e) {
e.printStackTrace();
}
}
Response response = sender.send();
if (response.isOK()) {
NutMap map = Json.fromJson(NutMap.class, response.getContent());
log.debug("getHttpToken:::" + Json.toJson(map));
if (0 == map.getInt("code")) {
return map.getString("data");
}
}
return "";
}
private static class TrustAllManager
implements X509TrustManager {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkServerTrusted(X509Certificate[] certs,
String authType) {
}
public void checkClientTrusted(X509Certificate[] certs,
String authType) {
}
}
public SSLContext createIgnoreVerifySSL() throws NoSuchAlgorithmException, KeyManagementException {
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, new TrustManager[]{new TrustAllManager()}, null);
return sc;
}
}
Uniapp 权限设置
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
Uniapp Webview 源码
<template>
<view>
<web-view :webview-styles="webviewStyles" src="http://192.168.4.108:5001/h5/" @message="showMessage"></web-view>
</view>
</template>
<script>
export default {
data() {
return {
webviewStyles: {
progress: {
color: '#FF3333'
}
},
qrCodeWv: null
}
},
onReady() {
// #ifdef APP-PLUS
let currentWebview = this.$scope.$getAppWebview()
setTimeout(() => {
this.wv = currentWebview.children()[0]
this.qrCodeWv = currentWebview.children()[0]
this.wv.setStyle({scalable:true})
},1000)
// #endif
},
methods: {
showMessage(event) {
if(event.detail.data && event.detail.data.length >0){
let dataInfo = event.detail.data[0]
console.log(dataInfo)
let type = dataInfo.type
if(type==='scanCode') {
this.startScanCode()
}
}
},
startScanCode() {
const self = this
uni.scanCode({
onlyFromCamera: false,
scanType: ['qrCode'],
success: function(res) {
setTimeout(() => {
const result = res.result.replace(/'/g,'"')
self.qrCodeWv.evalJS(`appScanCodeResult('${result}')`)
})
},
complete: function(args){
console.log(args)
}
})
}
}
}
</script>
<style>
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.logo {
height: 200rpx;
width: 200rpx;
margin-top: 200rpx;
margin-left: auto;
margin-right: auto;
margin-bottom: 50rpx;
}
.text-area {
display: flex;
justify-content: center;
}
.title {
font-size: 36rpx;
color: #8f8f94;
}
</style>
H5 Vue项目引入js
index.html 引入 public/js 下文件
<script src="<%= BASE_URL %>js/uni.webview.1.5.4.js"></script>
H5 main.js 定义回调方法和对象
window.appScanCodeResult = function (val) {
window.appScanCodeResultString = val
window.dispatchEvent(new CustomEvent("scanCodeResult"))
}
H5 Vue扫码页面代码
created() {
this.getDetailData()
window.addEventListener("scanCodeResult", this.handleAppScanCode, false)
document.addEventListener("UniAppJSBridgeReady", function () {
uni.getEnv(function (res) {
console.log("获取当前环境:" + JSON.stringify(res))
})
})
},
onBeforeDestroy() {
window.removeEventListener("scanCodeResult", this.handleAppScanCode)
},
methods: {
handleAppScanCode() {
const result = window.appScanCodeResultString
this.onScanSuccess(result)
},
// 扫码
saoCode() {
uni.postMessage({
data: {
type: "scanCode"
}
})
},
//扫码成功
onScanSuccess(val) {
this.tankCode = val
this._getFillingTankInfo()
}
}
https://github.com/RedisJSON/RedisJSON/releases
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rutsc --version
cargo build --release
loadmodule /usr/local/redis/module/librejson.dylib
重新启动 redis 后显示如下信息即可:
127.0.0.1:6379> module list
1) 1) "name"
2) "ReJSON"
3) "ver"
4) (integer) 20407