钉钉企业应用网关接入(保姆级教程)

article/2025/9/25 9:24:01

背景

在对接钉钉开放平台时, 会出现需要钉钉开放平台回调我们项目的情况. 而一般项目都被部署在公司内网.
因此, 我们需要进行内网穿透. 常用内网穿透工具对比如下表. 可以看到钉钉是在对接钉钉开放平台时, 最优的选择…
本文将详细介绍自己和钉钉企业应用网关对接和搭建的整体流程

常用内网穿工具透比较

在这里插入图片描述

企业应用网关

其实在之前, 钉钉还提供一种内网穿透. 但因为因安全合规、服务资源和维护成本等原因,钉钉于2022年7月21日起,不再提供内网穿透的工具服务,若需要在本地或开发测试环境调试中有内网穿透的需求,请参考文档自行搭建的frp内网穿透服务. 因此, 如果企业有这方面需求, 本着安全性, 稳定性, 简易性来说, 钉钉企业应用网关也是一个不错的选择. 而且可以联系商务搭建试用版平台. 点击进入官网地址

是什么

企业应用网关, 又称为钉钉零信任网关, 作用是为企业提供了内网应用在外网安全访问的能力,可以替代传统的VPN方案,基于阿里云的网络加速能力提升应用访问速度. 该产品以零信任为理念,提供持续动态的访问准入校验,最大程度上保障企业数字信息安全

用户痛点

企业通常会将核心应用系统放入企业内网或DMZ区,通过防火墙建立网络边界隔离.
如果企业员工在互联网侧通过移动设备或PC设备访问企业内网应用时,通过使用2种方式:VPN拨号和端口映射.
但是由于VPN存在设备漏洞或端口映射到外网IP或URL,黑客可以直接访问或攻击企业应用,导致企业核心数据泄露

在这里插入图片描述

企业原有内网应用访问方式,如下图所示

在这里插入图片描述

钉钉企业应用网关总体架构,如下图所示
在这里插入图片描述
企业用户内网应用访问推荐以下五种解决方案
在这里插入图片描述

怎么用

如果习惯看官网做的话, 可以参照企业应用网关配置流程 进行搭建.

搭建和踩坑过程

搭建过程

准备工作

  1. 已开通企业应用网关。如果未开通,需要使用移动端钉钉扫描下方二维码或进入此页面扫码二维码,
    并安装钉钉企业应用网关应用
    在这里插入图片描述
  2. 扫描后会出现下图页面
    如果企业开通该功能, 需要该账户级别为管理员(子管理员也不行)
    在这里插入图片描述

查看企业网关应用

  1. 当与客户侧沟通成功扫描上方二维码后,客户侧可以通过下图获取到开通成功的提示信息:
    在这里插入图片描述

  2. 在钉钉的管理后台 -> 工作台 -> 三方应用里面看到钉钉安全网关的入口,点击即可进入安全网关后台
    在这里插入图片描述
    进入企业网关首页
    在这里插入图片描述
    企业网关首页
    在这里插入图片描述

配置连通器

  1. 前提准备
    需要注意, 如果需要部署在linux服务器, 则需要满足下面条件(Windows也需保证满足如下条件)
    在这里插入图片描述

  2. 创建连通器
    选择新建连通器, 选择部署类型之后, 点击继续, 生成以下命令, 然后复制下面linux命令
    在这里插入图片描述

  3. 在linux服务器上进行执行复制的命令后, 可以看到连通器已经启动成功
    在这里插入图片描述

  4. 新建连通器组
    将新建的连通器加入连通器组, 连通器组的作用: 批量管理连通器, 在后面应用管理时使用
    在这里插入图片描述

补充——连通器自启动配置

2022.12.12补充, 公司在进行网络调整时, 由于网络断开过导致连通器断开, 为了图方便进行了服务器重启. 但是由于重启后连通器也未进行重启. 所以带来了一系列的问题. 需要配置重启后自启动. 当前服务器为ubuntu, 因此需要在/etc/rc.local 下面配置自启动项.

# 进入rc.local
vim  /etc/rc.local
# 配置启动名称 
## cd 后面跟的是连通器脚本所在目录, 就是执行第2步创建连通器是所在创建的目录
## ;(分号)后面执行的命令就是图2圈出来的参数!!!
cd /home/dingding-getway/connector ;./start.sh -a endpoint.ztna-dingtalk.com:8021 -k 6cc96011442d42149bdfbf95a1c61343 -s 42e4d95397dd4a75bf8b51e1ac655026df1786927f77f4a788e951b9d79fc55b &

图1
在这里插入图片描述
图2
在这里插入图片描述

配置应用管理

准备前提

  1. 新建应用
    方式一: 在管理后台-工作台新建第三方应用
    在这里插入图片描述

    方式二: 在钉钉开放平台根据使用场景去创建应用
    在这里插入图片描述

  2. 根据需要填写应用信息
    在这里插入图片描述

  3. 配置成功后, 自动生成AgentId, AppKey, AppSecret, 用于接口对接时使用
    在这里插入图片描述

  4. 发布应用(开发程序完成后使用)
    应用发布后, 才能被其他用户看到(这里指在自己的工作台上看到, 而不是管理后台的工作台上看到). 体验版本发布属于灰度发布.点击查看应用发布介绍
    在这里插入图片描述

配置应用

  1. 创建好应用之后, 在应用管理里面配置测试应用, 点击未配置的应用,进行相关配置
    在这里插入图片描述

  2. 应用管理 - 基础配置
    点击连通器选项的“+”,选择部署的连通器(或连通器组),与测试应用建立连接.
    选择域名进行配置, 这里的域名值得是内网地址+端口(可以配置多条映射). 配置好保存之后即可实现内网穿透!!!
    在这里插入图片描述

  3. 需要说明的是, 这里的主域名配置就是你在钉钉开放平台->具体应用->开发管理的应用首页地址. 如果后面配置规则之后访问出现unexpected EOF, 则说明配置的url在连通器上面访问不通, 一般情况就是在开发配置中, 配置的是https但实际上内网走的是http
    在这里插入图片描述 在这里插入图片描述
    例如我上图配置的内网域名是https, 但是实际上curl https://ip:port 则会出错. 说明该服务在内网本身是通过http访问的, 但是在网关域名中我误写成https导致网关穿透失败(因为该域名本身在内网都访问不通!!!)
    在这里插入图片描述

    因此在钉钉开放平台->具体应用->开发管理的应用首页地址之后访问不通时, 一定要验证该url在钉钉连通器所在服务器是否可以访问!

高级配置

注意:

  • 下面介绍下高级配置. 如无特殊需求可以不进行高级配置(直接进行下一步).
  • 这里配置需的前提是 要我们在下一步访问策略配置并获取外网访问域名后 再进行配置

因为背景介绍的原因, 需要钉钉开放平台在创建的应用中进行事件回调(在公司组织架构进行变动时调用该接口),
然而在我们配置好访问策略时, 钉钉回调接口时仍出现: url 地址访问异常, 不允许3xxx跳转
故需要我们管理平台-应用管理中进行高级配置. 没有配置前, 填入url后报错, 内容如下图所示

在这里插入图片描述

  1. 高级配置
    添加允许匿名访问url: 格式为 https://生成的外网访问域名/事件回调sdk接口地址.*
    在这里插入图片描述
  2. 配置成功后, 进行测试
    需要先将事件回调sdk部署在内网服务器上才能够进行测试
    在这里插入图片描述

配置访问策略

策略管理由企业管理员操作,包括注册策略、修改策略、删除策略、停用策略、启用策略、优先级排序等

  1. 然后点击“创建策略”按钮,进入创建策略界面
    在这里插入图片描述
  2. 在策略页面,填写策略信息,配置项如下表
    在这里插入图片描述
    在这里插入图片描述
  3. 点击完成,创建策略
    在这里插入图片描述

获取外网访问域名(踩坑)

  1. 在配置好访问策略后, 可以通过应用管理里面获取新域名, 而不是通过连通器显示的公网ip进行访问
    在这里插入图片描述

  2. 可以看到我们每次配置的内网ip+端口都被映射成外网对应可以访问的唯一域名, 根据需要复制新域名即可
    在这里插入图片描述

  3. 测试企业网关内网穿透效果
    内网访问
    在这里插入图片描述
    公网访问
    在这里插入图片描述

附: 钉钉事件回调sdk

事件回调介绍地址: https://open.dingtalk.com/document/org/configure-event-subcription

  1. 钉钉开放平台加解密方法

    
    import java.io.ByteArrayOutputStream;
    import java.nio.charset.Charset;
    import java.security.MessageDigest;
    import java.security.Permission;
    import java.security.PermissionCollection;
    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Random;
    import java.security.Security;
    import java.lang.reflect.Field;import javax.crypto.Cipher;
    import javax.crypto.spec.IvParameterSpec;
    import javax.crypto.spec.SecretKeySpec;import com.alibaba.fastjson.JSON;import org.apache.commons.codec.binary.Base64;/*** 钉钉开放平台加解密方法* 在ORACLE官方网站下载JCE无限制权限策略文件* JDK6的下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html* JDK7的下载地址: http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html* JDK8的下载地址 https://www.oracle.com/java/technologies/javase-jce8-downloads.html* @Author caoHaiYang* @Date 2022/8/18 10:15*/
    public class DingCallbackCrypto {private static final Charset CHARSET = Charset.forName("utf-8");private static final Base64 base64 = new Base64();private byte[] aesKey;private String token;private String corpId;/*** ask getPaddingBytes key固定长度**/private static final Integer AES_ENCODE_KEY_LENGTH = 43;/*** 加密随机字符串字节长度**/private static final Integer RANDOM_LENGTH = 16;/*** 构造函数** @param token          钉钉开放平台上,开发者设置的token* @param encodingAesKey 钉钉开放台上,开发者设置的EncodingAESKey* @param corpId         企业自建应用-事件订阅, 使用appKey*                       企业自建应用-注册回调地址, 使用corpId*                       第三方企业应用, 使用suiteKey** @throws DingTalkEncryptException 执行失败,请查看该异常的错误码和具体的错误信息*/public DingCallbackCrypto(String token, String encodingAesKey, String corpId) throws DingTalkEncryptException {if (null == encodingAesKey || encodingAesKey.length() != AES_ENCODE_KEY_LENGTH) {throw new DingTalkEncryptException(DingTalkEncryptException.AES_KEY_ILLEGAL);}this.token = token;this.corpId = corpId;aesKey = Base64.decodeBase64(encodingAesKey + "=");}public Map<String, String> getEncryptedMap(String plaintext) throws DingTalkEncryptException {return getEncryptedMap(plaintext, System.currentTimeMillis(), Utils.getRandomStr(16));}/*** 将和钉钉开放平台同步的消息体加密,返回加密Map** @param plaintext 传递的消息体明文* @param timeStamp 时间戳* @param nonce     随机字符串* @return* @throws DingTalkEncryptException*/public Map<String, String> getEncryptedMap(String plaintext, Long timeStamp, String nonce)throws DingTalkEncryptException {if (null == plaintext) {throw new DingTalkEncryptException(DingTalkEncryptException.ENCRYPTION_PLAINTEXT_ILLEGAL);}if (null == timeStamp) {throw new DingTalkEncryptException(DingTalkEncryptException.ENCRYPTION_TIMESTAMP_ILLEGAL);}if (null == nonce) {throw new DingTalkEncryptException(DingTalkEncryptException.ENCRYPTION_NONCE_ILLEGAL);}// 加密String encrypt = encrypt(Utils.getRandomStr(RANDOM_LENGTH), plaintext);String signature = getSignature(token, String.valueOf(timeStamp), nonce, encrypt);Map<String, String> resultMap = new HashMap<String, String>();resultMap.put("msg_signature", signature);resultMap.put("encrypt", encrypt);resultMap.put("timeStamp", String.valueOf(timeStamp));resultMap.put("nonce", nonce);return resultMap;}/*** 密文解密** @param msgSignature 签名串* @param timeStamp    时间戳* @param nonce        随机串* @param encryptMsg   密文* @return 解密后的原文* @throws DingTalkEncryptException*/public String getDecryptMsg(String msgSignature, String timeStamp, String nonce, String encryptMsg)throws DingTalkEncryptException {//校验签名String signature = getSignature(token, timeStamp, nonce, encryptMsg);if (!signature.equals(msgSignature)) {throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_SIGNATURE_ERROR);}// 解密String result = decrypt(encryptMsg);return result;}/** 对明文加密.* @param text 需要加密的明文* @return 加密后base64编码的字符串*/private String encrypt(String random, String plaintext) throws DingTalkEncryptException {try {byte[] randomBytes = random.getBytes(CHARSET);byte[] plainTextBytes = plaintext.getBytes(CHARSET);byte[] lengthByte = Utils.int2Bytes(plainTextBytes.length);byte[] corpidBytes = corpId.getBytes(CHARSET);ByteArrayOutputStream byteStream = new ByteArrayOutputStream();byteStream.write(randomBytes);byteStream.write(lengthByte);byteStream.write(plainTextBytes);byteStream.write(corpidBytes);byte[] padBytes = PKCS7Padding.getPaddingBytes(byteStream.size());byteStream.write(padBytes);byte[] unencrypted = byteStream.toByteArray();byteStream.close();Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16);cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);byte[] encrypted = cipher.doFinal(unencrypted);String result = base64.encodeToString(encrypted);return result;} catch (Exception e) {throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_ENCRYPT_TEXT_ERROR);}}/** 对密文进行解密.* @param text 需要解密的密文* @return 解密得到的明文*/private String decrypt(String text) throws DingTalkEncryptException {byte[] originalArr;try {// 设置解密模式为AES的CBC模式Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);// 使用BASE64对密文进行解码byte[] encrypted = Base64.decodeBase64(text);// 解密originalArr = cipher.doFinal(encrypted);} catch (Exception e) {throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_DECRYPT_TEXT_ERROR);}String plainText;String fromCorpid;try {// 去除补位字符byte[] bytes = PKCS7Padding.removePaddingBytes(originalArr);// 分离16位随机字符串,网络字节序和corpIdbyte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20);int plainTextLegth = Utils.bytes2int(networkOrder);plainText = new String(Arrays.copyOfRange(bytes, 20, 20 + plainTextLegth), CHARSET);fromCorpid = new String(Arrays.copyOfRange(bytes, 20 + plainTextLegth, bytes.length), CHARSET);} catch (Exception e) {throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_DECRYPT_TEXT_LENGTH_ERROR);}// corpid不相同的情况if (!fromCorpid.equals(corpId)) {throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_DECRYPT_TEXT_CORPID_ERROR);}return plainText;}/*** 数字签名** @param token     isv token* @param timestamp 时间戳* @param nonce     随机串* @param encrypt   加密文本* @return* @throws DingTalkEncryptException*/public String getSignature(String token, String timestamp, String nonce, String encrypt)throws DingTalkEncryptException {try {String[] array = new String[] {token, timestamp, nonce, encrypt};Arrays.sort(array);//System.out.println(JSON.toJSONString(array));StringBuffer sb = new StringBuffer();for (int i = 0; i < 4; i++) {sb.append(array[i]);}String str = sb.toString();System.out.println(str);MessageDigest md = MessageDigest.getInstance("SHA-1");md.update(str.getBytes());byte[] digest = md.digest();StringBuffer hexstr = new StringBuffer();String shaHex = "";for (int i = 0; i < digest.length; i++) {shaHex = Integer.toHexString(digest[i] & 0xFF);if (shaHex.length() < 2) {hexstr.append(0);}hexstr.append(shaHex);}return hexstr.toString();} catch (Exception e) {throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_SIGNATURE_ERROR);}}public static class Utils {public Utils() {}public static String getRandomStr(int count) {String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";Random random = new Random();StringBuffer sb = new StringBuffer();for (int i = 0; i < count; ++i) {int number = random.nextInt(base.length());sb.append(base.charAt(number));}return sb.toString();}public static byte[] int2Bytes(int count) {byte[] byteArr = new byte[] {(byte)(count >> 24 & 255), (byte)(count >> 16 & 255), (byte)(count >> 8 & 255),(byte)(count & 255)};return byteArr;}public static int bytes2int(byte[] byteArr) {int count = 0;for (int i = 0; i < 4; ++i) {count <<= 8;count |= byteArr[i] & 255;}return count;}}public static class PKCS7Padding {private static final Charset CHARSET = Charset.forName("utf-8");private static final int BLOCK_SIZE = 32;public PKCS7Padding() {}public static byte[] getPaddingBytes(int count) {int amountToPad = 32 - count % 32;if (amountToPad == 0) {amountToPad = 32;}char padChr = chr(amountToPad);String tmp = new String();for (int index = 0; index < amountToPad; ++index) {tmp = tmp + padChr;}return tmp.getBytes(CHARSET);}public static byte[] removePaddingBytes(byte[] decrypted) {int pad = decrypted[decrypted.length - 1];if (pad < 1 || pad > 32) {pad = 0;}return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad);}private static char chr(int a) {byte target = (byte)(a & 255);return (char)target;}}public static class DingTalkEncryptException extends Exception {public static final int SUCCESS = 0;public static final int ENCRYPTION_PLAINTEXT_ILLEGAL = 900001;public static final int ENCRYPTION_TIMESTAMP_ILLEGAL = 900002;public static final int ENCRYPTION_NONCE_ILLEGAL = 900003;public static final int AES_KEY_ILLEGAL = 900004;public static final int SIGNATURE_NOT_MATCH = 900005;public static final int COMPUTE_SIGNATURE_ERROR = 900006;public static final int COMPUTE_ENCRYPT_TEXT_ERROR = 900007;public static final int COMPUTE_DECRYPT_TEXT_ERROR = 900008;public static final int COMPUTE_DECRYPT_TEXT_LENGTH_ERROR = 900009;public static final int COMPUTE_DECRYPT_TEXT_CORPID_ERROR = 900010;private static Map<Integer, String> msgMap = new HashMap();private Integer code;static {msgMap.put(0, "成功");msgMap.put(900001, "加密明文文本非法");msgMap.put(900002, "加密时间戳参数非法");msgMap.put(900003, "加密随机字符串参数非法");msgMap.put(900005, "签名不匹配");msgMap.put(900006, "签名计算失败");msgMap.put(900004, "不合法的aes key");msgMap.put(900007, "计算加密文字错误");msgMap.put(900008, "计算解密文字错误");msgMap.put(900009, "计算解密文字长度不匹配");msgMap.put(900010, "计算解密文字corpid不匹配");}public Integer getCode() {return this.code;}public DingTalkEncryptException(Integer exceptionCode) {super((String)msgMap.get(exceptionCode));this.code = exceptionCode;}}static {try {Security.setProperty("crypto.policy", "limited");RemoveCryptographyRestrictions();} catch (Exception var1) {}}private static void RemoveCryptographyRestrictions() throws Exception {Class<?> jceSecurity = getClazz("javax.crypto.JceSecurity");Class<?> cryptoPermissions = getClazz("javax.crypto.CryptoPermissions");Class<?> cryptoAllPermission = getClazz("javax.crypto.CryptoAllPermission");if (jceSecurity != null) {setFinalStaticValue(jceSecurity, "isRestricted", false);PermissionCollection defaultPolicy = (PermissionCollection)getFieldValue(jceSecurity, "defaultPolicy", (Object)null, PermissionCollection.class);if (cryptoPermissions != null) {Map<?, ?> map = (Map)getFieldValue(cryptoPermissions, "perms", defaultPolicy, Map.class);map.clear();}if (cryptoAllPermission != null) {Permission permission = (Permission)getFieldValue(cryptoAllPermission, "INSTANCE", (Object)null, Permission.class);defaultPolicy.add(permission);}}}private static Class<?> getClazz(String className) {Class clazz = null;try {clazz = Class.forName(className);} catch (Exception var3) {}return clazz;}private static void setFinalStaticValue(Class<?> srcClazz, String fieldName, Object newValue) throws Exception {Field field = srcClazz.getDeclaredField(fieldName);field.setAccessible(true);Field modifiersField = Field.class.getDeclaredField("modifiers");modifiersField.setAccessible(true);modifiersField.setInt(field, field.getModifiers() & -17);field.set((Object)null, newValue);}private static <T> T getFieldValue(Class<?> srcClazz, String fieldName, Object owner, Class<T> dstClazz) throws Exception {Field field = srcClazz.getDeclaredField(fieldName);field.setAccessible(true);return dstClazz.cast(field.get(owner));}}
  2. Controller层接口

      /*** 事件回调方法** @param msg_signature* @param timeStamp* @param nonce* @param json* @return*/@RequestMapping("/callback")public Map<String, String> callBack(@RequestParam(value = "msg_signature", required = false) String msg_signature,@RequestParam(value = "timestamp", required = false) String timeStamp,@RequestParam(value = "nonce", required = false) String nonce,@RequestBody(required = false) JSONObject json) {try {// 1. 从http请求中获取加解密参数// 2. 使用加解密类型// Constant.OWNER_KEY 说明:// 1、开发者后台配置的订阅事件为应用级事件推送,此时OWNER_KEY为应用的APP_KEY。// 2、调用订阅事件接口订阅的事件为企业级事件推送,//      此时OWNER_KEY为:企业的appkey(企业内部应用)或SUITE_KEY(三方应用)DingCallbackCrypto callbackCrypto = new DingCallbackCrypto(dingTalkConfig.getToken(), dingTalkConfig.getAesKey(), dingTalkConfig.getAppkey());String encryptMsg = json.getString("encrypt");String decryptMsg = callbackCrypto.getDecryptMsg(msg_signature, timeStamp, nonce, encryptMsg);// 3. 反序列化回调事件json数据JSONObject eventJson = JSON.parseObject(decryptMsg);log.info("反序列化回调事件json数据:" + eventJson);String eventType = eventJson.getString("EventType");// 4. 根据EventType分类处理if ("check_url".equals(eventType)) {// 测试回调url的正确性log.info("测试回调url的正确性");} else if ("user_add_org".equals(eventType)) {// 处理通讯录用户增加事件log.info("发生了:" + eventType + "事件");} else {// 添加其他已注册的log.info("发生了:" + eventType + "事件");}// 5. 返回success的加密数据Map<String, String> successMap = callbackCrypto.getEncryptedMap("success");return successMap;} catch (DingCallbackCrypto.DingTalkEncryptException e) {e.printStackTrace();}return null;}
    
  3. 钉钉提供的事件回调列表
    其他类型请点击此蓝色链接获取
    在这里插入图片描述
    在这里插入图片描述

补充: 前端联调时, 需要为前端配置步骤

在复用钉钉登录功能之后, 前端进行本地调试时, 会进行网关内网穿透, 以便外网可测试访问.
当然也可以直接构造内网的二维码登录url, 但这里我们着重讨论第一种情况. 探究其解决方法

  1. 在钉钉企业应用网关平台, 配置对应应用的内网ip+端口到外网的映射
    在这里插入图片描述

  2. 找到并复制映射好的外网映射
    在这里插入图片描述

  3. 在钉钉开放平台的企业应用开发中, 选择自己的应用. 配置登录回调
    这里直接配置内网地址, 或者上一步映射好的外网地址均可
    在这里插入图片描述
    如果本步骤没有进行配置, 前端在访问该页面则会出现下面问题
    在这里插入图片描述


2023-01-03

补充: 如何配置网关连通器开机自启动

ubuntu

整体思路是按照这个大佬的博客走 Ubuntu设置自启动软件 本人使用的服务器是ubuntu 18.04

  1. 第一步:检查系统目录/lib/systemd/system/rc-local.service,如果没有自己新建,文件内容为(如果文件存在本身是没有[Install]项的,需要自己添加进去)

    #  SPDX-License-Identifier: LGPL-2.1+
    #
    #  This file is part of systemd.
    #
    #  systemd is free software; you can redistribute it and/or modify it
    #  under the terms of the GNU Lesser General Public License as published by
    #  the Free Software Foundation; either version 2.1 of the License, or
    #  (at your option) any later version.# This unit gets pulled automatically into multi-user.target by
    # systemd-rc-local-generator if /etc/rc.local is executable.
    [Unit]
    Description=/etc/rc.local Compatibility
    Documentation=man:systemd-rc-local-generator(8)
    ConditionFileIsExecutable=/etc/rc.local
    After=network.target[Service]
    Type=forking
    ExecStart=/etc/rc.local start
    TimeoutSec=0
    RemainAfterExit=yes
    GuessMainPID=no[Install]
    WantedBy=multi-user.target
    Alias=rc-local.service
    

    在这里插入图片描述

  2. etc目录下的文件也需要进行如上修改,检查/etc/systemd/system/rc-local.service,如果没有该文件则新增该文件

    	#  SPDX-License-Identifier: LGPL-2.1+##  This file is part of systemd.##  systemd is free software; you can redistribute it and/or modify it#  under the terms of the GNU Lesser General Public License as published by#  the Free Software Foundation; either version 2.1 of the License, or#  (at your option) any later version.# This unit gets pulled automatically into multi-user.target by# systemd-rc-local-generator if /etc/rc.local is executable.[Unit]Description=/etc/rc.local CompatibilityDocumentation=man:systemd-rc-local-generator(8)ConditionFileIsExecutable=/etc/rc.localAfter=network.target[Service]Type=forkingExecStart=/etc/rc.local startTimeoutSec=0RemainAfterExit=yesGuessMainPID=no[Install]WantedBy=multi-user.targetAlias=rc-local.service```
  3. 创建/etc/rc.local脚本文件,并写入想要运行的脚本命令

    vim /etc/rc.local
    

    在这里插入图片描述

  4. 给rc.local执行的权限

    sudo chmod +x /etc/rc.local
    
  5. 启用服务

    sudo systemctl enable rc-local
    sudo systemctl start rc-local.service
    sudo systemctl status rc-local.service
    

    在这里插入图片描述

  6. 重启电脑看效果
    通过重启后查看脚本启动的应用是否能够正常运行
    在这里插入图片描述

centos操作系统

参考 Linux设置开机自启动的三种方法

  1. 根据系统文件配置, 找到对应启动脚本, 系统文件所在位置 /lib/systemd/system/rc-local.service
    在这里插入图片描述

  2. 编辑启动脚本 vim /etc/rc.d/rc.local
    在这里插入图片描述

  3. 授予脚本可执行权限 chmod +x /etc/rc.d/rc.local

  4. 重启后测试软件是否启动
    在这里插入图片描述


http://chatgpt.dhexx.cn/article/UkqkMofJ.shtml

相关文章

码住!SpringCloud Gateway企业级网关详解及实践分享

Spring Cloud Gateway是Spring官方基于Spring5.0、SpringBoot2.0、Netty和Project Reactor等技术开发的网关&#xff0c;旨在为微服务框架提供一种简单而有效的统一的API路由管理方式&#xff0c;统一访问接口。 Spring Cloud Gateway作为Spring Cloud生态体系中的网关&#x…

RestCloud企业级网关,支持多种协议转换和接入

RestCloud企业级网关有别于基于Nginx的流量型网关&#xff0c;需要兼容所有业务系统的各种复杂协议&#xff0c;根据不同标准和报文进行数据格式转换映射&#xff0c;提供对所有业务系统API的集中鉴权、错误预警、数据加解密、协议转换、安全防护、日志审计等核心功能。可无缝与…

企业级API网关学习总结

网关的产生背景 微服务架构演变 单体架构 所有服务集中在单个项目中&#xff0c;每次部署需要部署整个项目 好处&#xff1a; 部署简单: 由于是完整的结构体&#xff0c;可以直接部署在一个服务器上即可。技术单一: 项目不需要复杂的技术栈&#xff0c;往往一套熟悉的技术…

模糊控制算法

一.基本原理 模糊计算是依据模糊规则&#xff0c;从几个控制变量的输入得到最终输出的过程&#xff0c;可分为模糊规则库&#xff0c;模糊化&#xff0c;推理方法和去模糊化四个模块。 二.matlab代码实现 (1)建立输入输出代码 &#xff08;2&#xff09;建立规则库代码 三.运行…

PLC模糊控制之模糊化

模糊控制的模糊化方法有很多种,我们这里主要以三角隶属度函数举例来讲 %输入语言变量:实际温度y与温度设定值ySP之差e=y-ySP及其变化率e/TS,TS为采样周期; %输出语言变量:控制通过加热装置的电流的可控硅导通角的变化量u. %温控系统为一个双输入单输出的模糊控制器。 %…

遗传算法优化模糊控制规则

声明&#xff1a;本博客只是为方便交流学习&#xff0c; 不得用于任何商业用途。内容涉及知识产权&#xff0c;版权所有&#xff0c;抄袭翻版必究。 目录&#xff1a; 遗传算法优化模糊控制规则 1、 系统辨识 2、 模糊控制器 3、 遗传算法 4、 代码实现 本文将系统且贯彻分析…

模糊控制基础算法

版权声明&#xff1a;本文为博主原创文章&#xff0c;未经博主允许不得转载。 https://blog.csdn.net/qq_34445388/article/details/79086584 模糊控制的工作原理&#xff1a; 从模糊控制器的构成我们知道&#xff0c;输入模糊化&#xff0c;模糊推理&#xff0c;去模糊化是实施…

模糊控制算法Fuzzy controller驾驶员制动意图识别模型,模糊控制算法,很好的模糊控制算法学习的例子

模糊控制算法Fuzzy controller驾驶员制动意图识别模型&#xff0c;模糊控制算法&#xff0c;很好的模糊控制算法学习的例子&#xff0c;有word操作&#xff0c;一看就会。 ID:1430638075361666牡丹城幽默的草莓

模糊控制算法在MATLAB/SIMULINK中的应用

模糊逻辑控制简称模糊控制&#xff0c;是以模糊集合论、模糊语言变量和模糊逻辑推理为基础的一种计算机数字控制技术。模糊控制实质上是一种非先行控制&#xff0c;从属于智能控制的范畴。模糊控制的一大特点是既有系统化的理论&#xff0c;又有大量的实际应用背景。 前面在学…

模糊控制算法实例解析(含代码)

首先来看一个实例&#xff0c;控制进水阀S1和出水阀S2&#xff0c;使水箱水位保持在目标水位O处。 按照日常操作经验&#xff0c;有以下规则&#xff1a; 1、 若当前水位高于目标水位&#xff0c;则向外排水&#xff0c;差值越大&#xff0c;排水越快&#xff1b; 2、 若当…

车辆换道决策的模糊控制算法实例

目录 一、模糊控制在换道决策应用上的概念介绍 1.1 模糊化 1.2 建立模糊规则 1.3 解模糊 二、Matlab建立模糊逻辑系统 2.1 Matlab模糊逻辑工具箱 2.2 建立模糊系统的步骤 2.3 建立换道决策的模糊逻辑系统 一、模糊控制在换道决策应用上的概念介绍 实际驾车时&#xff0c;…

模糊控制算法实例matlab程序

参考文献《智能控制——刘金锟》 以水位的模糊控制为例。如图4一4所示&#xff0c;设有一个水箱&#xff0c;通过调节阀可向内注水和向外抽水。设计一个模糊控制器&#xff0c;通过调节阀门将水位稳定在固定点附近。按照日常的操作经验&#xff0c;可以得到基本的控制规则为&am…

模糊控制算法的C++实现

在现代智能控制算法中&#xff0c;模糊控制是在实际控制系统设计中使用比较成熟的一种方法。模糊控制可以使用在一些无法建立系统模型的场合&#xff0c;根据专家经验确定模糊规则&#xff0c;实现对系统的控制。本篇文章适合对模糊控制算法有一定了解的人阅读&#xff0c;给大…

模糊控制算法基础知识

模糊控制的工作原理&#xff1a; 从模糊控制器的构成我们知道&#xff0c;输入模糊化&#xff0c;模糊推理&#xff0c;去模糊化是实施模糊控制的三个主要环节。有时根据这三个环节的作用分别称为&#xff1a;模糊器&#xff0c;模糊推理机和解模糊器。 所以要学会使用模糊控制…

控制算法(二)—— 模糊控制算法

模糊控制是以模糊集理论、模糊语言变量和模糊逻辑推理为基础的一种智能控制方法&#xff0c;它是从行为上模仿人的模糊推理和决策过程的一种智能控制算法。模糊控制首先将操作人员或专家经验编成模糊规则&#xff0c;然后将来自传感器的实时信号模糊化&#xff0c;将模糊化后的…

模糊控制(FL)算法

模糊控制算法(理论知识) 模糊&#xff08;Fuzzy&#xff09;控制是用语言归纳操作人员的控制策略&#xff0c;运用语言变量和模糊集合理论形成控制算法的一种控制。模糊控制的最重要特征是不需要建立被控对象精确的数学模型&#xff0c;只要求把现场操作人员的经验和数据总结成…

【学习笔记】模糊控制算法

本文目录 0. 前言1. 概述2. 模糊集合2.1 集合和论域2.2 模糊集合的概念2.3 模糊集合的表示方式2.4 模糊集合的运算 3. 模糊关系与模糊关系合成3.1 笛卡尔积3.2 关系与模糊关系3.3 模糊关系的运算3.4 模糊关系合成3.5 模糊变换 4. 模糊推理4.1 模糊推理规则【重要&#xff01;】…

图像处理——乘性噪声和加性噪声

加性噪声一般指热噪声、散弹噪声等&#xff0c;它们与信号的关系是相加&#xff0c;不管有没有信号&#xff0c;该类噪声是一直存在的。一般通信中把加性随机性看成是系统的背景噪声。 乘性噪声一般由信道不理想引起&#xff0c;它们与信号的关系是相乘&#xff0c;信号在它在…

给数据增加噪声

例如在POS-bp算法中增加噪声&#xff0c;优点&#xff1a;使输出更光滑从而提升网络的推理能力&#xff0c;提升泛化能力。添加样本噪声&#xff0c;使线条更光滑。 这个地方是添加了一个正太分布均值为0&#xff0c;方差为0.01的1xSamLnNum的随机数矩阵。

噪声模型

常见的噪声模型有(z是噪声值&#xff0c;m表示均值&#xff0c;s2表示方差)&#xff1a; 1、高斯噪声:ma , s2b^2 2、均匀分布噪声 3、伽马噪声 mb/a , s2b/a^2 4、指数分布噪声 m1/a , s21/a^2 5、瑞利噪声 : 6、椒盐噪声 其中盐表示亮点&#xff0c;椒表示暗点。 几…