通过 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;
pve 8.2
1、第一步,升级pve内核到最新
1)设置dns,让源域名可以解析
2)设置国内源
#编辑 Debian sources.list 源文件,写入国内源
nano /etc/apt/sources.list
# 内容如下:
deb https://mirrors.ustc.edu.cn/debian/ bookworm main contrib
deb-src https://mirrors.ustc.edu.cn/debian/ bookworm main contribe
deb https://mirrors.ustc.edu.cn/debian/ bookworm-updates main contrib
deb-src https://mirrors.ustc.edu.cn/debian/ bookworm-updates main contrib
# 编辑 PVE 源文件 pve-no-subscription.list
nano /etc/apt/sources.list.d/pve-no-subscription.list
# 内容如下:
deb https://mirrors.tuna.tsinghua.edu.cn/proxmox/debian bookworm pve-no-subscription
# 编辑屏蔽 PVE 企业源文件
nano /etc/apt/sources.list.d/pve-enterprise.list
# 将下面这一行注释掉 (前面加上井号)
# deb https://mirrors.tuna.tsinghua.edu.cn/proxmox/debian bookworm pve-no-subscription
3)升级内核
数据中心 – pve 节点 – 更新 – 升级
升级结束重启
2、第二步,虚拟机先安装系统
正常的虚拟机配置(默认配置),安装好飞牛OS系统。
BIOS选择 OVMF(UEFI)
BIOS 关闭 Secure Boot
处理器类型选择 host
显卡 默认
机型 默认
3、第三步,设置显卡直通
1)虚拟机停机,如果pve控制台停机不了,则网页进入飞牛系统关机。
pve节点配置参数
nano /etc/pve/qemu-server/101.conf
args%3A -set device.hostpci0.addr=02.0
飞牛os虚拟机设置:
2)显卡 选择 无
3)机型 选择 q35
5)添加PCI设备,选择原始设备 0000:00:02.0,勾选所有功能,主GPU、PCI-Express 不要勾选。
6)启动虚拟机,飞牛OS设置中查看是否显示GPU,影视播放启用硬解,并播放其他分辨率查看GPU占用情况。
ps:不同主机的GPU编号可能不同,比如 0000:00:02.0 ,device.hostpci0.addr=02.0
第一步:创建容器
docker create --name=tinymediamanager \
-e GROUP_ID=0 -e USER_ID=0 -e TZ=Asia/Shanghai \
-p 5800:5800 \
-p 5900:5900 \
dzhuang/tinymediamanager:latest-v5
PS: 网络已通过 https://wizzer.cn/archives/3804 配好,可以自动 pull 镜像文件
第二步:磁盘映射
NAS路径 -> 映射路径
/docker/ttm/config -> /config
/video -> /video
http://10.10.10.10:5800 访问,即可查看界面。
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 等时序数据库
原文地址:https://blog.chai.ac.cn/posts/docker-proxy
最后更新:2024年7月15日
最近又见识到了一些神奇的骚操作,考虑到在将来 Docker 的国内各个镜像站可能变得不可用,需要未雨绸缪一下。 有旁路由自然是好的,但现在打算用 Proxy 来解决这个问题。 由于群晖的 Container Manager 是基于 Docker 的,但部分配置路径不同,所以特意记录一下。
注意事项:
192.168.50.100:7893
群晖 Container Manager 用户可以跳过这一小节,你实际上已经有 Docker 了。
Docker Engine 安装过程请参考 官方文档。
Docker 官方给出了许多安装方式,我选择用 apt
从官方维护的源中安装。
你也可以选择手动下载二进制包,然后用 dpkg
安装.
这里选择使用 apt
演示,关键在于很多人还不清楚如何为 apt
设置代理:
shell
sudo vi /etc/apt/apt.conf
shell
Acquire::http::Proxy "http://192.168.50.100:7893";
Acquire::https::Proxy "http://192.168.50.100:7893";
注意第二个行依旧是 http
协议,否则会碰到 TLS Could not handshake 问题。 代理服务器只需要负责做请求转发和响应转发,不会像 HTTPS 协议一样进行解密和加密。
安装完成后,官网教程会让你运行 docker run hello-world
来验证安装是否成功。
默认情况下,你的本地肯定不存在任何有关镜像(如下所示),因此会从官方库拉取:
shell
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
c1ec31eb5944: Retrying in 1 second
docker: error pulling image configuration:
download failed after attempts=6: dial tcp 31.13.82.33:443: i/o timeout.
执行拉取操作的是 Docker Daemon,参考 官方文档 进行 Proxy 有关的设置:
提示
使用 docker info
可以查询到版本信息,版本太低的话请参考下一节的方法。
Docker Daemon 大多数配置选项都可根据 daemon.json
文件进行设置。
对于 Docker 引擎 23.0 及更高版本,可以在该文件中设置代理行为:
/etc/docker/daemon.json
~/.config/docker/daemon.json
/var/packages/ContainerManager/etc/docker.json
shell
{
"proxies": {
"http-proxy": "http://192.168.50.100:7893",
"https-proxy": "http://192.168.50.100:7893",
"no-proxy": "127.0.0.0/8"
}
}
这些配置将覆盖 docker.service
默认的 systemd 设定。
如果您位于 HTTP 或 HTTPS 代理服务器后面,例如在公司设置中, 则必须在 systemd 服务文件中指定守护程序代理配置,而不是在 daemon.json
文件中或使用环境变量。
如 Docker 版本太低,不支持通过 daemon.json
配置代理,则需手动创建 systemd 文件:
/etc/systemd/system/docker.service.d
~/.config/systemd/user/docker.service.d
/etc/systemd/system/pkg-ContainerManager-dockerd.service.d
添加 http-proxy.conf
文件,下面以群晖 Container Manager 为例:
shell
sudo mkdir -p /etc/systemd/system/pkg-ContainerManager-dockerd.service.d
sudo vi /etc/systemd/system/pkg-ContainerManager-dockerd.service.d/http-proxy.conf
shell
[Service]
Environment="HTTP_PROXY=http://192.168.50.100:7893"
Environment="HTTPS_PROXY=http://192.168.50.100:7893"
Environment="NO_PROXY=localhost,127.0.0.1"
如果你有内建的 registry-mirrors
, 记得加入 NO_PROXY
中。
不论采用上面哪种方式,都需要重启 Docker Daemon 服务:
synoservice
代替 systemctl
.systemctl --user
代替 sudo systemctl
.下面仅仅给出 root 模式和群晖 Container Manager 的重启方法:
sudo systemctl daemon-reload
sudo systemctl restart docker
sudo systemctl restart pkg-ContainerManager-dockerd.service
重启 Docker/Conatiner Manager 服务需要一定的时间,取决于你正在运行的容器数量。
检查设置是否生效:
sudo systemctl show --property=Environment docker
systemctl show --property=Environment pkg-ContainerManager-dockerd.service
再次跑 docker run hello-world
,应该就能成功了。
有的时候,你使用的 Docker 镜像在 build
和 run
时也需要代理。 大部分应该都知道怎么配置,或者会通过环境变量来设置。 但有的时候希望代理配置默认对所有容器生效(那为什么不用机器或路由级别的代理呢),可以参考下面的方法。
参考 官方文档 中的说明,你可以在 ~/.docker/config.json
中设置代理。
shell
{
"proxies": {
"default": {
"httpProxy": "http://192.168.50.100:7893",
"httpsProxy": "http://192.168.50.100:7893",
"noProxy": "127.0.0.0/8"
}
}
}
保存文件后配置将生效,适用于新容器的生成和运行,无需重启 Docker,
本质上,它通过影响 Docker CLI 来添加环境变量,效果类似于:
shell
docker build --build-arg HTTP_PROXY="http://192.168.50.100:7893" .
docker run --env HTTP_PROXY="http://192.168.50.100:7893" redis
但一般还是建议单独针对需要代理服务的容器手动设置这些环境变量, 同样地,一些 Docker 内的应用是不按照环境变量来设置代理的,需要手动配置,需要额外注意。 折腾了这么多,是不是还是觉得旁路由+规则代理的方法会更加简单呢?这就看个人需求了。
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>