Java系统对接企业微信审批项目流程
若依做的一个系统需求需要对接企业微信的人员去审核订单 回款之类,以下是详细步骤.
1.首先登入企业微信管理后台:
企业微信
2.找到应用管理
3.自建一个应用
4.这些数据都可以拿到
5.配置可信Ip
6.进入有两种方法让你去配置 ,第一种用公司的域名,然后下面有个文件需要导入,必须导入,不然不会成功的。导入之后配置自己电脑的IP公网地址。
7.配置回调的url接口
踩雷:::框架用的若依,这个接口必须开放,返回的也是字符串格式不能是json格式。
这里需要写两个接口 一个是get 一个是post。
8.创建自己的模板
9.模板显示范围和自建的应用必须关联
10.开发前必读 - 文档 - 企业微信开发者中心进入这个里面查看具体的教程
11.附所有的代码
wechat:# 企业IDcorpId: wwd# 审批应用IDagentId: 100# 审批应用SecretcorpSecret: 5vE# 回调校验的TokensToken: 2a# 回调校验的KeyencodingAesKey: 5JH4nvyvb1# tokenURLtokenUrl: https://qyapi.weixin.qq.com/cgi-bin/gettoken# 审核申请URLapprovalUrl: https://qyapi.weixin.qq.com/cgi-bin/oa/applyevent# 获取模板详情URLtemplateUrl: https://qyapi.weixin.qq.com/cgi-bin/oa/gettemplatedetail# 上传文件到企业微信URLuploadFileUrl: https://qyapi.weixin.qq.com/cgi-bin/media/upload# 获取用户信息urlgetUserUrl: https://qyapi.weixin.qq.com/cgi-bin/user/get# 添加实验区审批流模版IDareaTemplateId: 3WMV
package com.ruoyi.bingo.config.wechat;import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;/*** @author yj*/
@Data
@Component
public class WeChatConfig {@Value("${wechat.corpId}")private String corpId;@Value("${wechat.agentId}")private String agentId;@Value("${wechat.corpSecret}")private String corpSecret;@Value("${wechat.sToken}")private String sToken;@Value("${wechat.encodingAesKey}")private String encodingAesKey;@Value("${wechat.tokenUrl}")private String tokenUrl;@Value("${wechat.templateUrl}")private String templateUrl;@Value("${wechat.approvalUrl}")private String approvalUrl;@Value("${wechat.uploadFileUrl}")private String uploadFileUrl;@Value("${wechat.getUserUrl}")private String getUserUrl;@Value("${wechat.areaTemplateId}")private String areaTemplateId;@Value("${wechat.schoolTemplateId}")private String schoolTemplateId;@Value("${wechat.clientTemplateId}")private String clientTemplateId;@Value("${wechat.createOrderTemplateId}")private String createOrderTemplateId;@Value("${wechat.costTemplateId}")private String costTemplateId;@Value("${wechat.returnedTemplateId}")private String returnedTemplateId;@Value("${wechat.contractTemplateId}")private String contractTemplateId;@Value("${wechat.finishOrderTemplateId}")private String finishOrderTemplateId;// 提供公开的 Getter 方法public String getCorpId() {return corpId;}public String getAgentId() {return agentId;}public String getCorpSecret() {return corpSecret;}public String getSToken() {return sToken;}public String getEncodingAesKey() {return encodingAesKey;}public String getTokenUrl() {return tokenUrl;}public String getTemplateUrl() {return templateUrl;}public String getApprovalUrl() {return approvalUrl;}public String getUploadFileUrl() {return uploadFileUrl;}public String getGetUserUrl() {return getUserUrl;}public String getAreaTemplateId() {return areaTemplateId;}public String getSchoolTemplateId() {return schoolTemplateId;}public String getClientTemplateId() {return clientTemplateId;}public String getCreateOrderTemplateId() {return createOrderTemplateId;}public String getCostTemplateId() {return costTemplateId;}public String getReturnedTemplateId() {return returnedTemplateId;}public String getContractTemplateId() {return contractTemplateId;}public String getFinishOrderTemplateId() {return finishOrderTemplateId;}
}
12.加密解密XML工具栏
package com.ruoyi.bingo.config.wechat.xml;import com.ruoyi.common.utils.bean.BeanUtils;
import org.springframework.stereotype.Component;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.util.ArrayList;
import java.util.List;/*** Description :** @author YJ* date 2024-12-15* time 20:28*/
@Component
public class XmlUtils {/*** 解析xmL** @param xmlData*/public ApprovalBack analysis(String xmlData) {try {// 调用 fromXml 方法解析 XML 数据ApprovalCallback callback = ApprovalCallback.fromXml(xmlData);// 输出解析结果System.out.println("ToUserName: " + callback.toUserName);System.out.println("FromUserName: " + callback.fromUserName);System.out.println("CreateTime: " + callback.createTime);System.out.println("MsgType: " + callback.msgType);System.out.println("Event: " + callback.event);System.out.println("AgentID: " + callback.agentID);System.out.println("SpNo: " + callback.spNo);System.out.println("SpName: " + callback.spName);System.out.println("SpStatus: " + callback.spStatus);System.out.println("TemplateId: " + callback.templateId);System.out.println("ApplyTime: " + callback.applyTime);//System.out.println("Applyer UserId: " + callback.applyer.userId);//System.out.println("Applyer Party: " + callback.applyer.party);//System.out.println("SpRecord SpStatus: " + callback.spRecord.spStatus);//System.out.println("StatuChangeEvent: " + callback.statuChangeEvent);//输出 ProcessList 中的 SubNodeList 信息//if (callback.processList != null && callback.processList.nodeList != null) {// for (ApprovalCallback.UserInfo userInfo : callback.processList.nodeList.subNodeList) {// System.out.println("SubNode UserId: " + userInfo.userId);// }//}System.out.println("解密内容call: " + callback);ApprovalBack approvalBack = new ApprovalBack();approvalBack.setSpNo(callback.spNo);approvalBack.setSpStatus(callback.spStatus);return approvalBack;} catch (Exception e) {e.printStackTrace();}return null;}
}class ApprovalCallback {String toUserName;String fromUserName;String createTime;String msgType;String event;String agentID;String spNo;String spName;String spStatus;String templateId;String applyTime;Applyer applyer;SpRecord spRecord;String statuChangeEvent;ProcessList processList;public static ApprovalCallback fromXml(String xmlContent) throws Exception {DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();DocumentBuilder builder = factory.newDocumentBuilder();InputSource inputSource = new InputSource(new java.io.StringReader(xmlContent));Document doc = builder.parse(inputSource);ApprovalCallback callback = new ApprovalCallback();callback.toUserName = doc.getElementsByTagName("ToUserName").item(0).getTextContent();callback.fromUserName = doc.getElementsByTagName("FromUserName").item(0).getTextContent();callback.createTime = doc.getElementsByTagName("CreateTime").item(0).getTextContent();callback.msgType = doc.getElementsByTagName("MsgType").item(0).getTextContent();callback.event = doc.getElementsByTagName("Event").item(0).getTextContent();callback.agentID = doc.getElementsByTagName("AgentID").item(0).getTextContent();// 解析 ApprovalInfoorg.w3c.dom.NodeList approvalInfo = doc.getElementsByTagName("ApprovalInfo");if (approvalInfo.getLength() > 0) {Element approvalInfoElement = (Element) approvalInfo.item(0);callback.spNo = approvalInfoElement.getElementsByTagName("SpNo").item(0).getTextContent();callback.spName = approvalInfoElement.getElementsByTagName("SpName").item(0).getTextContent();callback.spStatus = approvalInfoElement.getElementsByTagName("SpStatus").item(0).getTextContent();callback.templateId = approvalInfoElement.getElementsByTagName("TemplateId").item(0).getTextContent();callback.applyTime = approvalInfoElement.getElementsByTagName("ApplyTime").item(0).getTextContent();// Applyer infoElement applyerElement = (Element) approvalInfoElement.getElementsByTagName("Applyer").item(0);callback.applyer = new Applyer();callback.applyer.userId = applyerElement.getElementsByTagName("UserId").item(0).getTextContent();callback.applyer.party = applyerElement.getElementsByTagName("Party").item(0).getTextContent();// SpRecord infoorg.w3c.dom.NodeList spRecordList = approvalInfoElement.getElementsByTagName("SpRecord");if (spRecordList.getLength() > 0) {Element spRecordElement = (Element) spRecordList.item(0);callback.spRecord = new SpRecord();callback.spRecord.spStatus = spRecordElement.getElementsByTagName("SpStatus").item(0).getTextContent();callback.spRecord.approverAttr = spRecordElement.getElementsByTagName("ApproverAttr").item(0).getTextContent();// Details infoorg.w3c.dom.NodeList detailsList = spRecordElement.getElementsByTagName("Details");if (detailsList.getLength() > 0) {Element detailsElement = (Element) detailsList.item(0);callback.spRecord.details = new Details();callback.spRecord.details.userId = detailsElement.getElementsByTagName("UserId").item(0).getTextContent();callback.spRecord.details.speech = detailsElement.getElementsByTagName("Speech").item(0).getTextContent();callback.spRecord.details.spStatus = detailsElement.getElementsByTagName("SpStatus").item(0).getTextContent();}}callback.statuChangeEvent = approvalInfoElement.getElementsByTagName("StatuChangeEvent").item(0).getTextContent();// ProcessList infoorg.w3c.dom.NodeList processList = approvalInfoElement.getElementsByTagName("ProcessList");if (processList.getLength() > 0) {Element processListElement = (Element) processList.item(0);callback.processList = new ProcessList();org.w3c.dom.NodeList nodeList = processListElement.getElementsByTagName("NodeList");if (nodeList.getLength() > 0) {Element nodeListElement = (Element) nodeList.item(0);org.w3c.dom.NodeList subNodeList = nodeListElement.getElementsByTagName("SubNodeList");callback.processList.nodeList = new ApprovalCallback.NodeList();callback.processList.nodeList.subNodeList = new ArrayList<>();for (int i = 0; i < subNodeList.getLength(); i++) {Element subNodeElement = (Element) subNodeList.item(i);UserInfo userInfo = new UserInfo();userInfo.userId = subNodeElement.getElementsByTagName("UserId").item(0).getTextContent();callback.processList.nodeList.subNodeList.add(userInfo);}}}}return callback;}public static class Applyer {private String userId;private String party;}public static class SpRecord {private String spStatus;private String approverAttr;private ApprovalCallback.Details details;}public static class Details {private String userId;private String speech;private String spStatus;}public static class ProcessList {private NodeList nodeList;}public static class NodeList {private List<UserInfo> subNodeList;}public static class UserInfo {private String userId;}
}
package com.ruoyi.bingo.config.wechat.xml;import lombok.Data;/*** Description :** @author YJ* date 2024-12-15* time 20:40*/
@Data
public class ApprovalBack {private String toUserName;private String createTime;private String msgType;private String event;private String agentID;//审批编号private String spNo;private String spName;private String spStatus;private String templateId;private String applyTime;private ApprovalCallback.Applyer applyer;private ApprovalCallback.SpRecord spRecord;private String statuChangeEvent;private ApprovalCallback.ProcessList processList;
}
13.controller层
@Anonymous
@Api(description = "对接企业微信")
@RestController
@RequestMapping("/enterprise/wechat")
public class EnterpriseWeChatController extends BaseController {@Autowiredprivate EnterpriseWeChatService weChatService;/*** 获取Access Token** @return*/@ApiOperation("获取Access Token")@PreAuthorize("@ss.hasPermi('bingo:wechat:accessToken')")@PostMapping("/accessToken")public String getAccessToken() {return weChatService.getAccessToken();}/*** 验证回调 URL 的有效性** @return*/@ApiOperation("验证回调 URL 的有效性")@GetMapping("/approval/back")public String approvalBack(@RequestParam("msg_signature") String msgSignature,@RequestParam("timestamp") String timestamp,@RequestParam("nonce") String nonce,@RequestParam("echostr") String ech,HttpServletResponse response) {return weChatService.approvalBack(msgSignature, timestamp, nonce, ech, response);}/*** 处理审批回调事件*/@PostMapping("/approval/back")public void handleApprovalCallback(@RequestParam("msg_signature") String msgSignature,@RequestParam("timestamp") String timestamp,@RequestParam("nonce") String nonce,HttpServletRequest request,HttpServletResponse response) {weChatService.handleApprovalCallback(msgSignature, timestamp, nonce, request, response);}/*** 获取企业微信用户信息** @return*/@ApiOperation("获取企业微信用户信息")@PreAuthorize("@ss.hasPermi('bingo:wechat:getUser')")@PostMapping("/getUser")public AjaxResult getUser(@RequestBody WechatUserDTO wechatUserDTO) {return success(weChatService.getUser(wechatUserDTO));}/*** 上传文件到企业微信** @return*/@ApiOperation("上传文件到企业微信")@PreAuthorize("@ss.hasPermi('bingo:wechat:upload')")@PostMapping("/upload")public AjaxResult upload(MultipartFile file) {return success(weChatService.uploadWeChat(file));}
}
14.server层
/*** 验证回调 URL 的有效性** @param msgSignature* @param timestamp* @param nonce* @param echostr* @param response* @return*/@Overridepublic String approvalBack(String msgSignature, String timestamp, String nonce, Stringechostr, HttpServletResponse response) {try {log.info("验证回调 URL 的有效性");return wxBizMsgCrypt.VerifyURL(msgSignature, timestamp, nonce, echostr);} catch (Exception e) {e.printStackTrace();log.error("验证回调 URL 的有效性");}return null;}
/*** 处理审批回调事件** @param msgSignature* @param timestamp* @param nonce* @param request* @param response*/@Overridepublic void handleApprovalCallback(String msgSignature, String timestamp, String nonce, HttpServletRequestrequest, HttpServletResponse response) {log.info("处理审批回调事件----------");try {// 读取回调数据StringBuilder sb = new StringBuilder();BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream()));String line;while ((line = reader.readLine()) != null) {sb.append(line);}String callbackXml = sb.toString();String decryptedMessage = wxBizMsgCrypt.DecryptMsg(msgSignature, timestamp, nonce, callbackXml);ApprovalBack analysis = xmlUtils.analysis(decryptedMessage);System.out.println("解密内容analysis: " + analysis);auditInfoMapper.updateAuditStatus(analysis);AuditInfo audit = auditInfoMapper.selectApprovalInfo(analysis.getSpNo());//实验区if (audit.getAuditType().equals(FileTypeConstants.FILE_AREA_TYPE)) {ExperimentArea experimentArea = new ExperimentArea();experimentArea.setId(audit.getId());experimentArea.setApprovalStatus(Integer.valueOf(analysis.getSpStatus()));areaMapper.updateById(experimentArea);}//实验校if (audit.getAuditType().equals(FileTypeConstants.FILE_SCHOOL_TYPE)) {ExperimentSchool experimentSchool = new ExperimentSchool();experimentSchool.setId(audit.getId());experimentSchool.setApprovalStatus(Integer.valueOf(analysis.getSpStatus()));schoolMapper.updateById(experimentSchool);}//客户if (audit.getAuditType().equals(FileTypeConstants.FILE_CLIENT_TYPE)) {Client client = new Client();client.setId(audit.getId());client.setApprovalStatus(Integer.valueOf(analysis.getSpStatus()));clientMapper.updateById(client);}//创建订单if (audit.getAuditType().equals(FileTypeConstants.FILE_CREATE_ORDER_TYPE)) {Orders orders = new Orders();orders.setId(audit.getId());orders.setApprovalCreateStatus(Integer.valueOf(analysis.getSpStatus()));ordersMapper.updateById(orders);}//费用if (audit.getAuditType().equals(FileTypeConstants.FILE_COST_TYPE)) {Cost cost = new Cost();cost.setId(audit.getId());cost.setApprovalStatus(Integer.valueOf(analysis.getSpStatus()));costMapper.updateById(cost);}//合同if (audit.getAuditType().equals(FileTypeConstants.FILE_CONTRACT_TYPE)) {Contract contract = new Contract();contract.setId(audit.getId());contract.setApprovalStatus(Integer.valueOf(analysis.getSpStatus()));contractMapper.updateById(contract);}//完成订单if (audit.getAuditType().equals(FileTypeConstants.FILE_FINISH_ORDER_TYPE)) {Orders orders = new Orders();orders.setId(audit.getId());orders.setApprovalFinishStatus(Integer.valueOf(analysis.getSpStatus()));ordersMapper.updateById(orders);}//回款if (audit.getAuditType().equals(FileTypeConstants.FILE_RETURNED_TYPE)) {Returned returned = new Returned();returned.setId(audit.getId());returned.setApprovalStatus(Integer.valueOf(analysis.getSpStatus()));returnedMapper.updateById(returned);}// 返回成功响应} catch (Exception e) {// 处理异常,例如返回错误信息throw new ServiceException(e.getMessage());}
callbackXml就是回调的xml,然后进行解密解析。 analysis 属于明文
ApprovalCallback callback = ApprovalCallback.fromXml(xmlData);
这个属于解密后所有的明文数据
15.提交审批的代码
/*** 创建订单申请** @param approvalOrderDTO* @return*/@Overridepublic String approvalOrder(ApprovalOrderDTO approvalOrderDTO) {ApprovalTemplateDTO approvalTemplateDTO = new ApprovalTemplateDTO();approvalTemplateDTO.setTemplate_id(weChatConfig.getCreateOrderTemplateId());try {String url = weChatConfig.getTemplateUrl() + "?access_token=" + getAccessToken();// 获取模板信息String jsonBody = new ObjectMapper().writeValueAsString(approvalTemplateDTO);String response = HttpUtils.sendPost(url, jsonBody);System.out.println("获取创建订单模板:" + response);JSONObject jsonResponse = new JSONObject(response);if (jsonResponse.getInt("errcode") != 0) {return null;}// 获取模板控件内容JSONObject templateContent = jsonResponse.getJSONObject("template_content");JSONArray controls = templateContent.getJSONArray("controls");// 构造字段内容JSONArray formArray = new JSONArray();//构造内容setArrayOrder(approvalOrderDTO, controls, formArray);String businessName = ordersMapper.selectOrderBusinessName(approvalOrderDTO.getBusinessType());approvalOrderDTO.setBusinessName(businessName);JSONObject applyData = new JSONObject();for (int i = 0; i < controls.length(); i++) {JSONObject control = controls.getJSONObject(i).getJSONObject("property");String controlType = control.getString("control");String controlId = control.getString("id");String title = control.getJSONArray("title").getJSONObject(0).getString("text");JSONObject field = new JSONObject();field.put("id", controlId);field.put("control", controlType);JSONObject fieldValue = new JSONObject();if ("Text".equals(controlType)) {switch (title) {case "客户名称":fieldValue.put("text", approvalOrderDTO.getClientName());break;case "业务类型":fieldValue.put("text", approvalOrderDTO.getBusinessName());break;case "合同总额":fieldValue.put("text", approvalOrderDTO.getOrderMoney());break;default:fieldValue.put("text", "无");break;}} else if ("File".equals(controlType)) {if (approvalOrderDTO.getMediaIdList() != null) {JSONArray filesArray = new JSONArray();for (String mediaId : approvalAreaDTO.getMediaIdList()) {JSONObject fileObject = new JSONObject();fileObject.put("file_id", mediaId);filesArray.put(fileObject);}fieldValue.put("files", filesArray);}} else if ("Textarea".equals(controlType)) {if (!StringUtils.isNull(approvalOrderDTO.getRemark())) {fieldValue.put("text", approvalOrderDTO.getRemark());}}field.put("value", fieldValue);formArray.put(field);}//处理子控件for (int i = 0; i < controls.length(); i++) {JSONObject control = controls.getJSONObject(i).getJSONObject("property");String controlType = control.getString("control");String controlId = control.getString("id");JSONObject resultObject = new JSONObject();resultObject.put("id", controlId);resultObject.put("control", controlType);if ("Table".equals(controlType)) {// 获取表格控件的配置信息JSONObject config = controls.getJSONObject(i).getJSONObject("config");JSONArray children = config.getJSONObject("table").getJSONArray("children");// 获取订单项目列表List<OrdersProjectListDTO> orderDetails = approvalOrderDTO.getOrdersProjectList();JSONArray jsonArray = new JSONArray(); // 用于存放所有表格行数据if (orderDetails != null && !orderDetails.isEmpty()) {for (OrdersProjectListDTO detail : orderDetails) {// 用于存放当前行的所有列数据JSONArray rowArray = new JSONArray();// 遍历表格模板的 children 数组,按模板字段顺序填充数据for (int n = 0; n < children.length(); n++) {JSONObject property = children.getJSONObject(n).getJSONObject("property");// 提取控件 ID、类型和标题String id = property.getString("id");String controlTypeInTable = property.getString("control");String tit = property.getJSONArray("title").getJSONObject(0).getString("text");// 填充对应字段值JSONObject value = new JSONObject();switch (tit) {case "合作项目":value.put("text", detail.getProjectName()); // 填充项目名称break;case "单价":value.put("text", detail.getUnitPrice()); // 填充单价break;case "数量":value.put("text", detail.getQuantity()); // 填充数量break;case "小计":value.put("text", detail.getSubtotal()); // 填充小计break;default:value.put("text", "未知字段"); // 其他字段使用默认值break;}// 构造当前列的数据结构JSONObject columnData = new JSONObject();columnData.put("id", id);columnData.put("control", controlTypeInTable);columnData.put("title", new JSONArray().put(new JSONObject().put("text", tit))); // 包含字段标题columnData.put("value", value);rowArray.put(columnData);}// 构造当前行数据JSONObject rowData = new JSONObject();rowData.put("list", rowArray);jsonArray.put(rowData); // 添加当前行到表格数据}// 遍历 JSON 数组for (int j = 0; j < formArray.length(); j++) {JSONObject obj = formArray.getJSONObject(j);// 判断是否是 Table 控件if ("Table".equals(obj.getString("control"))) {JSONObject value = obj.getJSONObject("value");value.put("children", jsonArray);}}}}}// 构造最终的审批请求applyData.put("contents", formArray);JSONObject auditRequest = new JSONObject();auditRequest.put("template_id", approvalTemplateDTO.getTemplate_id());auditRequest.put("creator_userid", approvalOrderDTO.getWechatUser());auditRequest.put("use_template_approver", 1);auditRequest.put("apply_data", applyData);System.out.println("构造最终的审批请求:" + auditRequest);// 调用企业微信审核接口String auditApiUrl = weChatConfig.getApprovalUrl() + "?access_token=" + getAccessToken();String res = HttpUtils.sendPost(auditApiUrl, auditRequest.toString());System.out.println("return:" + res);JSONObject jsonObject = new JSONObject(res);return jsonObject.getString("sp_no");} catch (Exception e) {e.printStackTrace();throw new ServiceException(ServiceConstants.USER_APPROVAL);}}
控件不一样,类型不一样 具体参照文档
给的例子全是value里面的参数
附带订单上传的json 格式
{"template_id": "C4ZULJy5ZkE7UuXWFu5rBfe1Xt7d91wbgpL2Rwrj1","creator_userid": "laoyou","use_template_approver": 1,"apply_data": {"contents": [{"id": "Text-1734055236654","control": "Text","value": {"text": "老友测试客户"}},{"id": "Text-1734055244339","control": "Text","value": {"text": "产品销售类"}},{"id": "Table-1734417961974","control": "Table","value": {"children": [{"list": [{"id": "Text-1734417976091","control": "Text","title": [{"text": "合作项目"}],"value": {"text": "A项目"}},{"id": "Text-1734417982652","control": "Text","title": [{"text": "单价"}],"value": {"text": 1}},{"id": "Text-1734417988404","control": "Text","title": [{"text": "数量"}],"value": {"text": 1}},{"id": "Text-1734417994187","control": "Text","title": [{"text": "小计"}],"value": {"text": 1}}]},{"list": [{"id": "Text-1734417976091","control": "Text","title": [{"text": "合作项目"}],"value": {"text": "B项目"}},{"id": "Text-1734417982652","control": "Text","title": [{"text": "单价"}],"value": {"text": 2}},{"id": "Text-1734417988404","control": "Text","title": [{"text": "数量"}],"value": {"text": 2}},{"id": "Text-1734417994187","control": "Text","title": [{"text": "小计"}],"value": {"text": 4}}]}]}},{"id": "Text-1734055362163","control": "Text","value": {"text": 5}},{"id": "Textarea-1734055383107","control": "Textarea","value": {"text": "这是一个订单"}},{"id": "File-1734055390371","control": "File","value": {"files": [{"file_id": "39EZFpTASgYAlxDf2S09ClA0TKaQcGPyQZPEQ9HbEwQN0AWwl4BLXy8MHyB5CG6yG"}]}}]}
}
加密解密下载链接
登录 - 企业微信开发者中心
相关文章:
Java系统对接企业微信审批项目流程
若依做的一个系统需求需要对接企业微信的人员去审核订单 回款之类,以下是详细步骤. 1.首先登入企业微信管理后台: 企业微信 2.找到应用管理 3.自建一个应用 4.这些数据都可以拿到 5.配置可信Ip 6.进入有两种方法让你去配置 ,第一种用公司的…...
MacOS 命令行详解使用教程
本章讲述MacOs命令行详解的使用教程,感谢大家观看。 本人博客:如烟花般绚烂却又稍纵即逝的主页 MacOs命令行前言: 在 macOS 上,Terminal(终端) 是一个功能强大的工具,它允许用户通过命令行直接与系统交互。本教程将详细介绍 macOS…...
易语言鼠标轨迹算法(游戏防检测算法)
一.简介 鼠标轨迹算法是一种模拟人类鼠标操作的程序,它能够模拟出自然而真实的鼠标移动路径。 鼠标轨迹算法的底层实现采用C/C语言,原因在于C/C提供了高性能的执行能力和直接访问操作系统底层资源的能力。 鼠标轨迹算法具有以下优势: 模拟…...
java-使用druid sqlparser将SQL DDL脚本转化为自定义的java对象
java-使用druid sqlparser将SQL DDL脚本转化为自定义的java对象 一、引言二、环境三、待解析的DDL四、解析后的对象结构五、完整的UT类六、控制台输出总结 一、引言 在日常开发中,有些需要对SQL进行解析的场景,比如读取表结构信息,生成文档、…...
靜態IP與DHCP的區別和用法
IP地址可以是靜態的,即固定不變,也可以是動態的,定期更改或每次連接後更改。 什麼是靜態 IP? 靜態IP地址是固定的,手動分配的IP地址,不會隨時間而變化。分配後,此 IP 保持不變,並由…...
【C#】Debug和Release的区别和使用
在 C 或 C# 开发中,Debug 和 Release 是两种不同的编译配置,主要用于开发过程中的不同阶段。它们的主要区别如下: 1. Debug 版本 Debug 版本是为了开发和调试程序而优化的构建模式。 特点: 包含调试信息: Debug 版本…...
Element plus 下拉框组件选中一个选项后显示的是 value 而不是 label
最近刚进行 Vue3 Element plus 项目实践,在进行表单二次封装的时候,表单元素 select 下拉框组件选中一个选项后显示的是 value 而不是 label,下面上代码: 原来的写法: <el-selectv-if"v.type select"…...
Redis - 消息队列 Stream
一、概述 消息队列 定义 消息队列模型:一种分布式系统中的消息传递方案,由消息队列、生产者和消费者组成消息队列:负责存储和管理消息的中间件,也称为消息代理(Message Broker)生产者:负责 产…...
【多维DP】【hard】力扣1269. 停在原地的方案数
有一个长度为 arrLen 的数组,开始有一个指针在索引 0 处。 每一步操作中,你可以将指针向左或向右移动 1 步,或者停在原地(指针不能被移动到数组范围外)。 给你两个整数 steps 和 arrLen ,请你计算并返回&…...
Android显示系统(11)- 向SurfaceFlinger申请Surface
Android显示系统(01)- 架构分析 Android显示系统(02)- OpenGL ES - 概述 Android显示系统(03)- OpenGL ES - GLSurfaceView的使用 Android显示系统(04)- OpenGL ES - Shader绘制三角…...
OpenCV实验篇:识别图片颜色并绘制轮廓
第三篇:识别图片颜色并绘制轮廓 1. 实验原理 颜色识别的原理: 颜色在图像处理中通常使用 HSV 空间来表示。 HSV 空间是基于人类视觉系统的一种颜色模型,其中: H(Hue):色调,表示颜色…...
鸿蒙-应用内悬浮窗
//悬浮窗工具类 import { window } from kit.ArkUI; import { BusinessError } from kit.BasicServicesKit; import { Logger } from mbbase/common-ui; import * as FloatedWindowPage from ./FloatedWindowPage; // 导入命名路由页面 const TAG [FloatedWindowUtils]; ex…...
Ubuntu Linux操作系统
一、Ubuntu简介 Ubuntu Linux是由南非人马克沙特尔沃思(Mark Shuttleworth)创办的基于Debian Linux的操作系统,于2004年10月公布。Ubuntu是一个以桌面应用为主的Linux发行版操作系统。Ubuntu拥有庞大的社区力量,用户可以方便地从社区获得帮助。其官方网…...
Linux下SVN客户端保存账号密码
参考文章:解决:Linux上SVN 1.12版本以上无法直接存储明文密码_linux svn 保存密码-CSDN博客新版本svn使用gpg-agent存储密码-CSDN博客svn之无法让 SVN 存储密码,即使配置设置为允许_编程设计_ITGUEST 方法一:明文方式保存密码 首…...
【DBeaver】连接带kerberos的hive[Apache|HDP]
目录 一、安装配置Kerberos客户端环境 1.1 安装Kerberos客户端 1.2 环境配置 二、基于Cloudera驱动创建连接 三、基于Hive原生驱动创建连接 一、安装配置Kerberos客户端环境 1.1 安装Kerberos客户端 在Kerberos官网下载,地址如下:https://web.mit.edu/kerberos…...
Android-Glide详解
目录 一,介绍 二,使用 三,源码分析思路 四,with源码分析 五,模拟Glide生命周期管理 一,介绍 Glide目前是安卓最主流的加载图片的框架,也是源码最为复杂的框架之一。 要想完完全全吃透Glide的源…...
【容器】k8s学习笔记原理详解(十万字超详细)
Pod详解 Pod介绍 Pod结构 每个Pod中都可以包含一个或者多个容器,这些容器可以分为两类: 用户程序所在的容器,数量可多可少Pause容器,这是每个Pod都会有的一个根容器,它的作用有两个: 可以以它为依据&am…...
SQL Server通过存储过程实现自定义邮件格式并定时发送
在 SQL Server 中,可以通过存储过程实现自定义邮件格式并定时发送。这通常涉及以下几个步骤: 1. 配置 Database Mail:首先需要配置 SQL Server 的 Database Mail 功能。 2. 创建存储过程:编写存储过程来生成自定义邮件格式并发送邮件。 3. 设置 SQL Server 代理作…...
通过增强的 vSphere 集成增强你的 vSphere 监控
作者:来自 Elastic Ishleen Kaur•Lalit Satapathy vSphere 是 VMware 的云计算虚拟化平台,提供一套功能强大的虚拟化资源管理套件。它允许组织创建、管理和优化虚拟环境,提供高可用性、负载平衡和简化资源分配等高级功能。vSphere 可以高效利…...
C++ 并发专题 - C++线程同步的几种方法
一:概述 线程同步是多线程编程中的一个重要概念,它用于控制多个线程之间对共享资源的访问,避免竞态条件(race condition)和数据不一致的问题。线程同步确保在多线程环境中,多个线程访问共享数据时能够按照某…...
[java]网络编程
java.net.*包下提供了网络编程的解决方案 通信架构 CS架构 客户端 客户端需要开发 用户需要安装 服务端 需要开发 BS架构 浏览器 不需要开发 需要安装浏览器 服务器 需要开发 网络通信三要素 IP地址 是设备在网络中的唯一标识, 全称 互联网协议地址 分类 公网IP 可…...
[C++]类的继承
一、什么是继承 1.定义: 在 C 中,继承是一种机制,允许一个类(派生类)继承另一个类(基类)的成员(数据和函数)。继承使得派生类能够直接访问基类的公有和保护成员…...
2024安装hexo和next并部署到github和服务器最新教程
碎碎念 本来打算写点算法题上文所说的题目,结果被其他事情吸引了注意力。其实我之前也有过其他博客网站,但因为长期不维护,导致数据丢失其实是我懒得备份。这个博客现在部署在GitHub Pages上,github不倒,网站不灭&…...
【spring】@Qualifier注解
目录 1. 说明2. 用法示例2.1 标注在字段上2.2 标注在方法上2.3 标注在类上2.4 在自定义注解上的应用 3. 注意事项 1. 说明 1.Qualifier是Spring框架中的一个注解,主要用于解决依赖注入时的歧义性问题。2.定义:Qualifier是一个限定符注解,用于…...
uniapp 应用的生命周期、页面的生命周期、组件的生命周期
uniapp 作为一款跨平台的移动应用开发框架,其生命周期分为应用生命周期、页面生命周期和组件生命周期。下面分别介绍这三种生命周期的具体内容: 应用生命周期 应用生命周期仅适用于整个应用,在 App.vue 中可以监听到以下生命周期函数&#…...
热更新解决方案4——xLua热补丁
概述 运行时不在执行C#中的代码,而是执行Lua中的代码,相当于是打了个补丁。 1.第一个热补丁 2.多函数替换 3.协程函数替换 在原HotfixMain脚本中只加个协程函数即可(和在Start中启动协程函数) 4.索引器和属性替换 在HotfixMain中…...
【MARL】深入理解多智能体近端策略优化(MAPPO)算法与调参
📢本篇文章是博主强化学习(RL)领域学习时,用于个人学习、研究或者欣赏使用,并基于博主对相关等领域的一些理解而记录的学习摘录和笔记,若有不当和侵权之处,指出后将会立即改正,还望谅…...
3.2.1.2 汇编版 原子操作 CAS
基本原理说明 在 x86 和 ARM 架构上,原子操作通常利用硬件提供的原子指令来实现,比如 LOCK 前缀(x86)或 LDREX/STREX(ARM)。以下是一些关键的原子操作(例如原子递增和比较交换)的汇…...
关于llama2:从原始llama-2-7b到llama-2-7b-hf的权重转换教程
1.首先,我是从各个教程里面选了一个实际操作的教程(这样更加靠谱):下载llama2-7b并转hf模型_huggingface 下载llama2-7b-chat-hf-CSDN博客 2.但是,其实我在另外一篇更好的教程里面看到过一个坑(这篇好像是腾…...
物理机内网穿透
前言: 本文主要讲述如何使用内网穿透以及其安全性。 将带领大家在公网上搭建几个常用靶场。 一,什么是内网穿透。 大多数情况下,我们的个人电脑都处于内网,即没有可公开访问的独立 IP 地址,因此其他内网用户找不到…...
构建一个rust生产应用读书笔记四(实战1)
我们需要从访客那里收集哪些信息,以便将其登记为电子邮件通讯的订阅者? 电子邮件地址:这是最基本的要求,因为我们需要通过电子邮件地址向订阅者发送内容。姓名:虽然这不是强制性的,但我们希望收集一个名字…...
[LeetCode-Python版]206. 反转链表(迭代+递归两种解法)
题目 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。 示例 1: 输入:head [1,2,3,4,5] 输出:[5,4,3,2,1] 示例 2: 输入:head [1,2] 输出:[2,1] 示例 3࿱…...
shardingsphere分库分表跨库访问 添加分片规则
shardingsphere分库分表跨库访问 添加分片规则 建立 JDBC 环境 创建表 t_order: CREATE TABLE t_order (tid bigint(20) NOT NULL,tname varchar(255) DEFAULT NULL,goods_id bigint(20) DEFAULT NULL,tstatus varchar(255) DEFAULT NULL,PRIMARY KEY (tid) ) E…...
【NLP 15、深度学习处理文本】
目录 一、反向传播 编辑 1.反向传播运算过程 2.前向传播和反向传播的作用 前向传播 反向传播 3.定义模型(torch包) 4.手动实现 ① 线性层 ② sigmoid激活函数 ③ 手动实现MSE均方差损失函数 ④ 前向传播 ⑤ 手动实现梯度计算 ⑤ 权重的更新:…...
Android Studio创建新项目并引入第三方so外部aar库驱动NFC读写器读写IC卡
本示例使用设备:https://item.taobao.com/item.htm?spma21dvs.23580594.0.0.52de2c1bbW3AUC&ftt&id615391857885 一、打开Android Studio,点击 File> New>New project 菜单,选择 要创建的项目模版,点击 Next 二、输入项目名称…...
3D视觉[一]3D计算机视觉
3D视觉[一]3D计算机视觉 3D计算机视觉概述 像机标定 文章目录 3D视觉[一]3D计算机视觉前言一、人类视觉二、计算机视觉2.1 计算机视觉的研究目的2.2 计算机视觉的研究任务2.3 计算机视觉的研究方法2.4 视觉计算理论2.5 马尔框架中计算机视觉表达的四个层次2.5.1 图像ÿ…...
Linux权限(超详细彻底搞懂Linux的权限)
🔥个人主页🔥:孤寂大仙V 🌈收录专栏🌈:Linux 🌹往期回顾🌹:Linux常见指令(初学者必看) 🔖流水不争,争的是滔滔不 一、Linux下的两种用户超级用户&…...
Ubuntu22.04安装docker desktop遇到的bug
1. 确认已启用 KVM 虚拟化 如果加载了模块,输出应该如下图。说明 Intel CPU 的 KVM 模块已开启。 否则在VMware开启宿主机虚拟化功能: 2. 下一步操作: Ubuntu | Docker Docs 3. 启动Docker桌面后发现账户登陆不上去: Sign in | …...
网新恒天八股总结
Java的基本数据类型 四类八种 整数类型:byte,short,int,long 浮点类型:float,double 字符类型:char 布尔类型:boolean char类型的范围 0 ~ 65535,可以表示16位无符号整数 equals和的区别 &,&&a…...
【AIGC】与模型对话:理解与预防ChatGPT中的常见误解
博客主页: [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: AIGC | ChatGPT 文章目录 💯前言💯模型的工作原理和用户期望差异人工智能模型的基本工作原理认知上的局限与误解用户期望与模型实际能力的差距精确理解用户意图的重要性实际应用中的建议 &…...
09篇--图片的水印添加(掩膜的运用)
如何添加水印? 添加水印其实可以理解为将一张图片中的某个物体或者图案提取出来,然后叠加到另一张图片上。具体的操作思想是通过将原始图片转换成灰度图,并进行二值化处理,去除背景部分,得到一个类似掩膜的图像。然后…...
Qt 使用modbus协议
Qt 框架下 使用modbus协议 一,使用Qt原生的 QModbusClient ,比如QModbusTcpClient 1,因为modbus的读写 需要在同一个线程中,所以需要在主线程中利用moveToThread的方式,将业务逻辑封装到 子线程中。 2,m…...
pip离线安装一个github仓库
要使用pip安装一个本地Git仓库,你可以按照以下步骤操作: 确保你已经克隆了Git仓库到本地。 进入仓库所在的目录。 使用pip安装。 以下是具体的命令: 克隆Git仓库到本地(替换下面的URL为你的仓库URL) git clone https…...
【ETCD】【源码阅读】深入分析 storeTxnWrite.Put方法源码
该方法是 storeTxnWrite 类型中的核心方法,负责将键值对存储到数据库,同时处理键的元数据(如版本、修订号、租约)并管理租约关联。 目录 一、完整代码二、方法详解方法签名1. 计算修订号并初始化变量2. 检查键是否已存在3. 生成索…...
桥接模式的理解和实践
桥接模式(Bridge Pattern),又称桥梁模式,是一种结构型设计模式。它的核心思想是将抽象部分与实现部分分离,使它们可以独立地进行变化,从而提高系统的灵活性和可扩展性。本文将详细介绍桥接模式的概念、原理…...
【Rust自学】3.2. 数据类型:标量类型
3.2.0. 写在正文之前 欢迎来到Rust自学的第三章,一共有6个小节,分别是: 变量与可变性数据类型:标量类型(本文)数据类型:复合类型函数和注释控制流:if else控制流:循环 通过第二章…...
【Leetcode Top 100】199. 二叉树的右视图
问题背景 给定一个二叉树的 根节点 r o o t root root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。 数据约束 二叉树的节点个数的范围是 [ 0 , 100 ] [0,100] [0,100] − 100 ≤ N o d e . v a l ≤ 100…...
Java并发编程框架之其他并发工具
选错了就选错了,不要一遍一遍的后悔,总是一遍遍的想,当时怎么样就好了,不要欺负当时的自己,当时你一个人站在迷雾中,也很迷茫,就算重新来一遍,以你当时的阅历和心智,还是…...
MinerU:PDF文档提取工具
目录 docker一键启动本地配置下载模型权重文件demo.py使用命令行启动GPU使用情况 wget https://github.com/opendatalab/MinerU/raw/master/Dockerfile docker build -t mineru:latest .docker一键启动 有点问题,晚点更新 本地配置 就是在Python环境中配置依赖和…...
Unity性能优化---使用SpriteAtlas创建图集进行批次优化
在日常游戏开发中,UI是不可缺少的模块,而在UI中又使用着大量的图片,特别是2D游戏还有很多精灵图片存在,如果不加以处理,会导致很高的Batches,影响性能。 比如如下的例子: Batches是9࿰…...