fyz недель назад: 2
Сommit
f04b66a0a3

+ 61 - 0
mjava-hangshi/pom.xml

@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>java-mcli</artifactId>
+        <groupId>com.malk</groupId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>mjava-hangshi</artifactId>
+    <description>上海航食</description>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <!-- 核心模块-->
+        <dependency>
+            <groupId>com.malk</groupId>
+            <artifactId>mjava</artifactId>
+            <version>${mjava.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.xerial</groupId>
+            <artifactId>sqlite-jdbc</artifactId>
+            <version>3.41.2.1</version>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>2.1.1.RELEASE</version>
+                <configuration>
+                    <includeSystemScope>true</includeSystemScope>
+                    <!-- 如果没有该配置,devtools不会生效: 打包时关闭 -->
+                    <fork>false</fork>
+                    <!-- 避免中文乱码 -->
+                    <jvmArguments>-Dfile.encoding=UTF-8</jvmArguments>
+                </configuration>
+                <!-- 允许生成可运行jar -->
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+        <finalName>${project.artifactId}</finalName>
+    </build>
+</project>

+ 33 - 0
mjava-hangshi/src/main/java/com/malk/hangshi/Boot.java

@@ -0,0 +1,33 @@
+package com.malk.hangshi;
+
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
+
+import javax.persistence.EntityManager;
+
+/**
+ * corp项目: 扫描公共模块
+ * -
+ * 若是无需数据库模块, 配置无效地址也可启动, 引入mjava不支持直接 @SpringBootApplication(exclude = DataSourceAutoConfiguration.class) 配置
+ * 需要配置 jpa.hibernate.ddl-auto 为 none. 标识对表没有任何操作. 若不设置为 none, flyway.enabled 配置会无效, 在没有数库连接情况下程序无法启动
+ */
+@EnableJpaAuditing
+@SpringBootApplication(scanBasePackages = {"com.malk"},exclude={DataSourceAutoConfiguration.class})
+public class Boot {
+
+    public static void main(String... args) {
+        SpringApplication.run(Boot.class, args);
+    }
+
+    /**
+     * 让Spring管理JPAQueryFactory [不使用Qualifier详见mjava-Boot]
+     */
+    @Bean
+    public JPAQueryFactory jpaQueryFactory(EntityManager entityManager) {
+        return new JPAQueryFactory(entityManager);
+    }
+}

+ 22 - 0
mjava-hangshi/src/main/java/com/malk/hangshi/controller/DDController.java

@@ -0,0 +1,22 @@
+package com.malk.hangshi.controller;
+
+import com.malk.controller.DDCallbackController;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 钉钉事件回调 3_1
+ * -
+ * [子项目直接继承即可有调用, 无需实现]
+ * -
+ * 注解 @RequestMapping 路径不能重复 [主子项目属同一个项目];
+ * 获取项目回调请求地址, https://mc.cloudpure.cn/frp/xxx/dd/callback [调试代理: frp + nginx]
+ */
+@Slf4j
+@RestController
+@RequestMapping("/dd")
+public class DDController extends DDCallbackController {
+
+
+}

+ 117 - 0
mjava-hangshi/src/main/java/com/malk/hangshi/controller/DLController.java

@@ -0,0 +1,117 @@
+package com.malk.hangshi.controller;
+
+/**
+ * 错误抛出与拦截详见 CatchException
+ */
+
+import cn.hutool.core.util.ObjectUtil;
+import com.malk.hangshi.service.PayService;
+import com.malk.server.common.McException;
+import com.malk.server.common.McR;
+import com.malk.server.common.McREnum;
+import com.malk.service.aliwork.YDClient;
+import com.malk.service.aliwork.YDService;
+import com.malk.utils.UtilMap;
+import com.malk.utils.UtilServlet;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Map;
+
+@Slf4j
+@RestController
+@RequestMapping
+public class DLController {
+
+    @Autowired
+    private YDService ydService;
+    @Autowired
+    private YDClient ydClient;
+
+    @Autowired
+    private PayService payService;
+
+    @PostMapping(value = "testToken")
+    McR testToken() {
+        payService.testToken();
+        return McR.success();
+    }
+
+    @PostMapping(value = "testGet")
+    McR testGet(@RequestBody Map data) {
+        payService.getInvestmentCompany(UtilMap.getBoolean(data,"isLastTime"));
+        return McR.success();
+    }
+    /**
+     * 流程下发操作
+     */
+    @SneakyThrows
+    @PostMapping("meetingIssue")
+    McR meetingIssue(@RequestBody Map data) {
+        log.info("开始执行流程下发操作");
+        payService.meetingIssue(data.get("formInstanceId").toString());
+        return McR.success();
+    }
+    /**
+     * 通用流程发起
+     */
+    @SneakyThrows
+    @PostMapping("process/start")
+    McR startProcess(@RequestBody Map data) {
+        log.info("开始执行流程发起操作");
+//        Map header = UtilServlet.getHeaders(request);
+        log.info("流程发起, {}", data);
+//        McException.assertException(!"dingspvmfolrjzhak6ge".equals(header.get("authorization")), McREnum.NOT_AUTHORIZED);
+        if ("班组".equals(UtilMap.getString(data,"type"))){
+            Map result = payService.startProcess(data.get("formInstanceId").toString(), data.get("depId").toString(), data.get("serial").toString());
+            if ("error".equals(result.get("dtUrl"))){
+                return McR.error("4001","创建审批实例失败");
+            }
+        }
+        return McR.success();
+    }
+    /**
+     * 撤销流程发起
+     */
+    @SneakyThrows
+    @PostMapping("process/terminate")
+    McR terminateProcess(@RequestBody Map data, HttpServletRequest request) {
+        log.info("开始执行流程撤销操作");
+        Map header = UtilServlet.getHeaders(request);
+        log.info("流程发起, {}, {}", data, header);
+        McException.assertException(!"dingspvmfolrjzhak6ge".equals(header.get("authorization")), McREnum.NOT_AUTHORIZED);
+        Map result = payService.terminateProcess(data);
+        return McR.success(result);
+    }
+
+    /**
+     * 更新审批实例
+     */
+    @SneakyThrows
+    @PostMapping("process/update")
+    McR updateProcess(@RequestBody Map data, HttpServletRequest request, @RequestParam String code) {
+        log.info("开始执行流程更新操作");
+        Map header = UtilServlet.getHeaders(request);
+        log.info("流程发起, {}, {}", data, header);
+        McException.assertException(!"dingspvmfolrjzhak6ge".equals(header.get("authorization")), McREnum.NOT_AUTHORIZED);
+            return McR.success();
+    }
+    /**
+     * 创建视频会议
+     */
+    @PostMapping("createVideoMeeting")
+    McR createVideoMeeting(@RequestBody Map data) {
+        String userId = UtilMap.getString(data, "userId");
+        String title = UtilMap.getString(data, "title");
+        if (ObjectUtil.isEmpty(userId)||ObjectUtil.isEmpty(title)){
+            log.info("用户id或会议标题不能为空");
+            return McR.error("400","用户id或会议标题不能为空");
+        }
+        log.info("创建视频会议,userid:{},title:{}", userId,title);
+        String videoMeeting = payService.createVideoMeeting(userId, title);
+        return McR.success(UtilMap.map("url",videoMeeting));
+    }
+}

+ 69 - 0
mjava-hangshi/src/main/java/com/malk/hangshi/delegate/DDDelegate.java

@@ -0,0 +1,69 @@
+package com.malk.hangshi.delegate;
+
+import com.malk.delegate.DDEvent;
+import com.malk.hangshi.service.PayService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Primary;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+/**
+ * OA审批事件 [主项目也有实现, 添加 @Primary 优先注入主项目实现]
+ * -
+ * 取消方案: 撤销和拒绝流程不继续执行连接器, 因此不使用连接器与轮询审批记录方案 [低效而且占用较高钉钉api调次数];
+ * 优化方案: 通过事件订阅实现实时同步所有审批状态, 定时查询钉钉回调失败记录 [配置钉钉事件Delegate, 添加定时任务]
+ */
+@Slf4j
+@Service
+@Primary
+public class DDDelegate implements DDEvent {
+
+    @Autowired
+    PayService payService;
+    // 审批任务回调执行业务逻辑
+
+    private static final String DXCPXS = "PROC-3F7AC816-B41B-4A48-8CFD-CE9FF92B05AB";
+    private static final String NDKJXS = "PROC-A9F9C430-5947-4B4F-9D8A-2591207E6153";
+    private static final String YPSQ = "PROC-B6CCD0BE-E74D-4FCD-8B3A-B68F7827C5AC";
+    private static final String KS = "PROC-B5937B5F-7883-4BAD-932A-F54A5113576C";
+
+    @Async
+    @Override
+    public void executeEvent_Task_Finish(String processInstanceId, String processCode, boolean isAgree, String remark) {
+        log.info("executeEvent_Task_Finish");
+    }
+
+    @Async
+    @Override
+    public void executeEvent_Task_Start(String processInstanceId, String processCode) {
+        log.info("executeEvent_Task_Start");
+    }
+
+    @Async
+    @Override
+    public void executeEvent_Task_Redirect(String processInstanceId, String processCode) {
+        log.info("executeEvent_Task_Redirect");
+    }
+
+    @Async
+    @Override
+    public void executeEvent_Instance_Start(String processInstanceId, String processCode) {
+        log.info("executeEvent_Instance_Start");
+    }
+
+
+    // 审批实例回调执行业务逻辑
+    @Async
+    @Override
+    public void executeEvent_Instance_Finish(String processInstanceId, String processCode, boolean isAgree, boolean isTerminate, String staffId) {
+        log.info("executeEvent_Instance_Finish");
+        String approveResult = isAgree ? "agree" : "refuse";
+//        if (isTerminate) approveResult = "terminated"
+//        log.info("审批实例回调执行业务逻辑, {}", approveResult);
+        if (isAgree && processCode.equals("PROC-597774D7-7E42-4C88-AC1D-06CA945E70C9")){
+            payService.updateMeeting(processInstanceId);
+            log.info("agree:{},terminate:{}",isAgree,isTerminate);
+        }
+    }
+}

+ 37 - 0
mjava-hangshi/src/main/java/com/malk/hangshi/entity/DocumentHeader.java

@@ -0,0 +1,37 @@
+package com.malk.hangshi.entity;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+@Data
+public class DocumentHeader implements Serializable {
+    private static final long serialVersionUID = 4820142670157009063L;
+    private String external_id;
+    private String header_type_code;
+    private String status;
+    private String created_by_code;
+    private String submit_user_code;
+    private Long submit_date;
+    private String branch_code;
+    private String submit_department_code;
+    private String column1;
+    private Long start_datetime;
+    private Long end_datetime;
+    private String column2;
+    private String destination_city_name;
+    private String destination_cities;
+    private String destination_city_to_name;
+    private String column15;
+    private String column16;
+    private String description;
+    private BigDecimal total_amount;
+    private BigDecimal total_claim_amount;
+    private BigDecimal total_pay_amount;
+    private BigDecimal total_net_amount;
+    private BigDecimal total_tax_amount;
+    private BigDecimal total_pay_currency_amount;
+
+
+}

+ 50 - 0
mjava-hangshi/src/main/java/com/malk/hangshi/schedule/ScheduleTask.java

@@ -0,0 +1,50 @@
+package com.malk.hangshi.schedule;
+
+import com.malk.hangshi.service.PayService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.annotation.Scheduled;
+
+/**
+ * @EnableScheduling 开启定时任务 [配置参考McScheduleTask]
+ */
+@Slf4j
+@Configuration
+@EnableScheduling
+@ConditionalOnProperty(name = {"spel.scheduling"})
+public class ScheduleTask {
+
+
+    @Autowired
+    private PayService payService;
+
+    /**
+     * 每天15,16点尝试下发流程
+     */
+    @Scheduled(cron = "0 0 15-16 * * ?")
+    void updateUseCar(){
+        log.info("每天15,16点尝试下发流程");
+        payService.getInvestmentCompany(false);
+    }
+
+    /**
+     * 每天17点推送最终版
+     */
+    @Scheduled(cron = "0 0 17 * * ?")
+    void updateUseCarLast(){
+        log.info("每天17点尝试下发流程");
+        payService.getInvestmentCompany(true);
+    }
+    /**
+     * 每5分钟更新确认人数
+     */
+    @Scheduled(cron = "0 0/5 * * * ?")
+    void updateYidaMeeting(){
+        log.info("每5分钟更新确认人数");
+        payService.updateYidaMeeting();
+    }
+
+}

+ 27 - 0
mjava-hangshi/src/main/java/com/malk/hangshi/service/DingTalkService.java

@@ -0,0 +1,27 @@
+package com.malk.hangshi.service;
+
+import java.util.List;
+import java.util.Map;
+
+public interface DingTalkService {
+
+
+    /**
+     * 获取在职人员
+     * @param access_token
+     * @param status_list
+     * @param offset
+     * @param size
+     * @return
+     */
+    Map getAllUserIdInfo(String access_token, String status_list, Number offset,Number size);
+
+    /**
+     * 获取员工花名册字段信息
+     * @param access_token
+     * @param userId
+     * @return
+     */
+    List<Map> getDDUserInfoById(String access_token, String userId, Number appAgentId);
+
+}

+ 32 - 0
mjava-hangshi/src/main/java/com/malk/hangshi/service/Impl/DingTalkServiceImpl.java

@@ -0,0 +1,32 @@
+package com.malk.hangshi.service.Impl;
+
+
+import com.malk.hangshi.service.DingTalkService;
+import com.malk.server.dingtalk.DDR;
+import com.malk.utils.UtilMap;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Map;
+
+@Service
+@Slf4j
+public class DingTalkServiceImpl implements DingTalkService {
+
+
+    @Override
+    public Map getAllUserIdInfo(String access_token, String status_list, Number offset, Number size) {
+        Map param = UtilMap.map("access_token", access_token);
+        Map body = UtilMap.map("status_list, offset, size", status_list, offset,size);
+        return (Map) DDR.doPost("https://oapi.dingtalk.com/topapi/smartwork/hrm/employee/queryonjob", null, param, body).getResult();
+    }
+
+    @Override
+    public List<Map> getDDUserInfoById(String access_token, String userId, Number appAgentId) {
+        Map param = UtilMap.map("access_token", access_token);
+        Map body = UtilMap.map("userid_list, agentid", userId, appAgentId);
+        return (List<Map>) DDR.doPost("https://oapi.dingtalk.com/topapi/smartwork/hrm/employee/v2/list", null, param, body).getResult();
+    }
+
+}

+ 525 - 0
mjava-hangshi/src/main/java/com/malk/hangshi/service/Impl/PayServiceImpl.java

@@ -0,0 +1,525 @@
+package com.malk.hangshi.service.Impl;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.malk.hangshi.service.DingTalkService;
+import com.malk.hangshi.service.PayService;
+import com.malk.server.aliwork.YDConf;
+import com.malk.server.aliwork.YDParam;
+import com.malk.server.dingtalk.DDConf;
+import com.malk.server.dingtalk.DDR;
+import com.malk.server.dingtalk.MDR;
+import com.malk.service.aliwork.YDClient;
+import com.malk.service.aliwork.YDService;
+import com.malk.service.dingtalk.*;
+import com.malk.utils.UtilMap;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Service
+@Slf4j
+public class PayServiceImpl implements PayService {
+
+    @Autowired
+    private DDClient ddClient;
+    @Autowired
+    private DDClient_Workflow ddClient_workflow;
+    @Autowired
+    private DDClient_Meeting ddClientMeeting;
+    @Autowired
+    private DDClient_Contacts ddClient_contacts;
+    @Autowired
+    private DDClient_Personnel ddClientPersonnel;
+    @Value("${dingtalk.appKey}")
+    private String APP_EKY;
+    @Value("${dingtalk.appSecret}")
+    private String APP_SECRET;
+    @Value("${dingtalk.agentId}")
+    private Long agentId;
+
+    @Autowired
+    DingTalkService dingTalkService;
+    @Autowired
+    DDService ddService;
+    @Autowired
+    private YDClient ydClient;
+    @Autowired
+    private YDService ydService;
+
+    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+
+    private static final String API_TOKEN = "/corpAccessToken/get/V2";
+    private static final String POST_GET_USER = "/user/getByMobile";
+    private static final String POST_DATA_UPDATE = "/crm/custom/v2/data/update";
+    private static final String TABLE_PRODUCT_FIELID = "TableField_BEUZFS0B8R40";
+    private static final String TABLE_CELLLINES_FIELID = "TableField_M04O3XGALA80";
+    private static  String CROPID = "";
+    private ConcurrentHashMap<String, LocalDateTime> formInstanceIdStore = new ConcurrentHashMap<>();
+
+    //所有的枚举
+    private static final Map<String, String> EMPLOYEE_ENUM = new HashMap<>();
+
+    static {
+        EMPLOYEE_ENUM.put("下属公司","employeeField_mji9tann");
+        EMPLOYEE_ENUM.put("一线部门","employeeField_mji9tany");
+        EMPLOYEE_ENUM.put("室","employeeField_mji9tao9");
+        EMPLOYEE_ENUM.put("班组","employeeField_mjiac7gr");
+    }
+    //运行控制子表枚举
+    private static final Map<String, String> OPERATION_CONTROL = new HashMap<>();
+
+    static {
+        OPERATION_CONTROL.put("投资公司","tableField_mjavj2r6");
+        OPERATION_CONTROL.put("下属公司","tableField_mji9tanq");
+        OPERATION_CONTROL.put("一线部门","tableField_mji9tao1");
+        OPERATION_CONTROL.put("室","tableField_mji9taoc");
+    }
+    //安全生产子表枚举
+    private static final Map<String, String> WORK_SAFETY = new HashMap<>();
+
+    static {
+        WORK_SAFETY.put("投资公司","tableField_mjgiipn0");
+//        WORK_SAFETY.put("下属公司","tableField_mji9tant");
+//        WORK_SAFETY.put("一线部门","tableField_mji9tao4");
+//        WORK_SAFETY.put("室","tableField_mji9taof");
+    }
+    //质量控制子表枚举
+    private static final Map<String, String> QUALITY_CONTROL = new HashMap<>();
+
+    static {
+        QUALITY_CONTROL.put("投资公司","tableField_mjgiipn4");
+//        QUALITY_CONTROL.put("下属公司","tableField_mji9tanw");
+//        QUALITY_CONTROL.put("一线部门","tableField_mji9tao7");
+//        QUALITY_CONTROL.put("室","tableField_mji9taoi");
+    }
+    private YDParam.YDParamBuilder _initLYParam() {
+        return YDParam.builder()
+                .appType("APP_PG9VWQVT23CRFBOOOG6Y")
+                .systemToken("73D66971EQO1KI40NT9M96FHV3BK3CZNBQGJMWX1");
+
+    }
+    /**
+     * token存储
+     * @param formInstanceId
+     * @param expireMinutes
+     */
+    public void addToken(String formInstanceId, int expireMinutes) {
+        log.info("缓存添加id:{}",formInstanceId);
+        LocalDateTime expirationTime = LocalDateTime.now().plusMinutes(expireMinutes);
+        formInstanceIdStore.put(formInstanceId, expirationTime);
+    }
+    /**
+     * 清理过期token
+     * @param token
+     * @return
+     */
+    public boolean validateToken(String token) {
+        LocalDateTime expirationTime = formInstanceIdStore.get(token);
+        if (expirationTime == null || expirationTime.isBefore(LocalDateTime.now())) {
+            formInstanceIdStore.remove(token); // 清理过期 Token
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 发起流程审批
+     * @param formInstanceId
+     * @param depId 部门id
+     * @return 流程实例id和地址
+     */
+    @Override
+    public Map<String, Object> startProcess(String formInstanceId, String depId, String serial) {
+        log.info("开始发起流程审批");
+        addToken(formInstanceId + ",0",1440);
+        //获取部门下所有员工id
+        List<String> workingEmployeeIds = ddClient_contacts.listDepartmentUserId(ddClient.getAccessToken(), Long.parseLong(depId));
+        List<String> instanceIds = new ArrayList<>();
+        //发送失败的userid
+        List<String> errorEmployeeIds = new ArrayList<>();
+
+        Map formData = ydClient.queryData(_initLYParam()
+                        .formInstId(formInstanceId).build()
+                , YDConf.FORM_QUERY.retrieve_id).getFormData();
+
+        //整合子表
+        List<List<Map>> lastList = new ArrayList<>();
+        extracted(formData, lastList, OPERATION_CONTROL, "(一)运行控制");
+        extracted(formData, lastList, WORK_SAFETY, "(二)安全生产");
+        extracted(formData, lastList, QUALITY_CONTROL, "(三)质量控制");
+
+        String meetingTitle = UtilMap.getString(formData, "textField_mjsiv3xl");
+        List banzus = UtilMap.getList(formData, "departmentSelectField_mjb7ixr6");//班组
+        List yixianbumen = UtilMap.getList(formData, "departmentSelectField_mjb7ixr6");//一线部门
+
+        try {
+            workingEmployeeIds.forEach(e->{
+                List<Map> formValue = new ArrayList<>();
+                List<String> empList = new ArrayList<>();
+//                empList.add(e);
+                empList.add("153620324221442254");
+                formValue.add(UtilMap.map("name, value","流水号",serial));
+                formValue.add(UtilMap.map("name, value","宣讲明细",JSON.toJSONString(lastList)));
+                formValue.add(UtilMap.map("name, value","宜搭班组会id",formInstanceId));
+                formValue.add(UtilMap.map("name, value","班组会会议主题",meetingTitle));
+//                formValue.add(UtilMap.map("name, value","creator",e));
+                formValue.add(UtilMap.map("name, value","creator","153620324221442254"));
+                formValue.add(UtilMap.map("name, value","联系人", JSON.toJSONString(empList)));
+//                String result = ddClient_workflow.doProcessInstancesNew(ddClient.getAccessToken(), e, "PROC-597774D7-7E42-4C88-AC1D-06CA945E70C9", formValue, depId);
+                String result = ddClient_workflow.doProcessInstancesNew(ddClient.getAccessToken(), "153620324221442254", "PROC-597774D7-7E42-4C88-AC1D-06CA945E70C9", formValue, depId);
+                JSONObject jsonObject = JSONObject.parseObject(result);
+                if (!ObjectUtil.isNotNull(jsonObject.get("instanceId"))){
+                    errorEmployeeIds.add(e);
+                }else {
+                    instanceIds.add(jsonObject.get("instanceId").toString());
+                }
+            });
+
+        } catch (Exception e) {
+            log.info("发起流程失败:{}",e);
+            e.printStackTrace();
+        }
+
+        String url = "https://applink.dingtalk.com/approval/detail?corpId=ding226da4276814d290a1320dcb25e91351&instanceId=";
+        return UtilMap.map("ddProcessId, dtUrl, errorEmpIds",  instanceIds,  url,errorEmployeeIds);
+    }
+
+    private static void extracted(Map formData, List<List<Map>> lastList, Map<String, String> currentMap, String type) {
+        List<Map> details = new ArrayList<>();
+        final String[] content = {""};
+        currentMap.forEach((k1,v1)->{
+            List<Map> sonList = (List<Map>) formData.get(v1);
+            if (ObjectUtil.isNotNull(sonList) && sonList.size() > 0){
+                sonList.forEach(s->{
+                    s.forEach((k2,v2)->{
+                        content[0] = content[0] + v2 + System.lineSeparator();
+                    });
+                });
+            }
+        });
+        details.add(UtilMap.map("name, value","内容",content[0]));
+        details.add(UtilMap.map("name, value","类型",type));
+        lastList.add(details);
+
+    }
+
+    /**
+     * 终止流程
+     * @param data 传参
+     * @return 是否成功
+     */
+    @Override
+    public Map terminateProcess(Map data) {
+        boolean isSystem = false;
+        if (data.containsKey("isSystem") && ObjectUtil.isNotNull(data.get("isSystem"))){
+            isSystem = UtilMap.getBoolean(data,"isSystem");
+        }
+        String operatingUserId = "";
+        if (data.containsKey("operatingUserId")){
+            operatingUserId = UtilMap.getString(data,"operatingUserId");
+        }
+        boolean isOk = ddClient_workflow.terminateRunningApproveNew(ddClient.getAccessToken(), UtilMap.getString(data, "processInstanceId"), isSystem, UtilMap.getString(data, "remark"), operatingUserId);
+        return UtilMap.map("result",isOk);
+    }
+
+    @Override
+    public void meetingIssue(String formInstanceId) {
+        LocalDate today = LocalDate.now();
+        String dateStr = today.format(formatter);
+        Map formData = ydClient.queryData(_initLYParam()
+                        .formInstanceId(formInstanceId)
+                        .useLatestVersion(true)
+                        .build(),
+                YDConf.FORM_QUERY.retrieve_id).getFormData();
+        List department = UtilMap.getList(formData, "departmentSelectField_mjb7ixr6_id");
+        log.info("开始执行下发流程,部门类型:{},id:{}",UtilMap.getString(formData, "selectField_mjavj2ry"),formInstanceId);
+        List<Map> dataList = new ArrayList<>();
+
+        //如果是投资公司下发,则查询所有下属公司,因为在部门档案表中上级部门无法选择投资公司
+        if (UtilMap.getString(formData, "selectField_mjavj2ry").equals("投资公司")){
+            //写入AI表格
+            insertAIExcel(formData);
+
+            dataList = ydService.queryFormData_all(_initLYParam()
+                    .formUuid("FORM-651D1713A25E423FB4EC1A5A06A7A741QWEY")
+                    .searchFieldJson(JSON.toJSONString(UtilMap.map("selectField_mjgj9non", "下属公司")))
+                    .build());
+        }else {
+            dataList = ydService.queryFormData_all(_initLYParam()
+                    .formUuid("FORM-651D1713A25E423FB4EC1A5A06A7A741QWEY")
+                    .searchFieldJson(JSON.toJSONString(UtilMap.map("departmentSelectField_mjgqcjsw", department.get(0))))
+                    .build());
+        }
+        dataList.forEach(e->{
+            List issueDepartment = UtilMap.getList(e, "departmentSelectField_mjgj9noi_id");
+            List issueEmployee = UtilMap.getList(e, "employeeField_mjgj9noh_id");
+            String depType = UtilMap.getString(e,"selectField_mjgj9non");
+
+            formData.put("selectField_mjavj2ry",depType);
+            formData.put("departmentSelectField_mjb7ixr6",issueDepartment);
+            formData.put("dateField_mjb860nt",System.currentTimeMillis());
+
+            formData.put("employeeField_mji9tann",UtilMap.getList(formData, "employeeField_mji9tann_id"));
+            formData.put("employeeField_mji9tany",UtilMap.getList(formData, "employeeField_mji9tany_id"));
+            formData.put("employeeField_mji9tao9",UtilMap.getList(formData, "employeeField_mji9tao9_id"));
+            formData.put(EMPLOYEE_ENUM.get(UtilMap.getString(e,"selectField_mjgj9non")),issueEmployee);
+            /*发起流程*/
+            if (depType.equals("班组")){
+                String classType = UtilMap.getString(e,"radioField_mke10pcc");
+
+                List<Map> mapList1 = (List<Map>) formData.get(OPERATION_CONTROL.get("投资公司"));
+                List<Map> mapList2 = (List<Map>) formData.get(WORK_SAFETY.get("投资公司"));
+                List<Map> mapList3 = (List<Map>) formData.get(QUALITY_CONTROL.get("投资公司"));
+
+                final String[] content = {""};
+                if (mapList1.size()>0 && ObjectUtil.isNotNull(mapList1)){
+                    content[0] = content[0] + "(一)运行控制 ";
+                    mapList1.forEach(e1->{
+                        e1.forEach((k,v)->{
+                            content[0] = content[0] + v;
+                        });
+                    });
+                }
+                if (mapList2.size()>0 && ObjectUtil.isNotNull(mapList2)) {
+                    content[0] = content[0] + "(二)安全生产 ";
+                    mapList2.forEach(e1 -> {
+                        e1.forEach((k, v) -> {
+                            content[0] = content[0] + v;
+                        });
+                    });
+                }
+                if (mapList3.size()>0 && ObjectUtil.isNotNull(mapList3)) {
+                    content[0] = content[0] + "(三)质量控制 ";
+                    mapList3.forEach(e1 -> {
+                        e1.forEach((k, v) -> {
+                            content[0] = content[0] + v;
+                        });
+                    });
+                }
+                formData.put("textField_mjkuqqx4",issueDepartment.get(0));
+                formData.put("numberField_mjqsi2ut",0);
+                formData.put("radioField_mke10pcc",classType);//是否外包
+                formData.put("textareaField_mke0p9o9",content[0]);//上级部门内容
+                formData.put("textField_mjsiv3xl",UtilMap.getList(e, "departmentSelectField_mjgj9noi").get(0).toString()+dateStr+"班前会");//会议主题
+                ydClient.operateData(_initLYParam()
+                        .formUuid("FORM-18921AB3A1AD4CC6853EB7631B9ABA0AHCF3")
+                        .formDataJson(JSON.toJSONString(formData))
+                        .build(), YDConf.FORM_OPERATION.start);
+            }else {
+                ydClient.operateData(_initLYParam()
+                        .formUuid("FORM-609926407D9D4CABB771228200AEE57EKGSE")
+                        .formDataJson(JSON.toJSONString(formData))
+                        .build(), YDConf.FORM_OPERATION.start);
+
+            }
+        });
+    }
+
+    /**
+     * 写入AI表格
+     * @param formData
+     */
+    private void insertAIExcel(Map formData) {
+        log.info("开始执行插入AI表格");
+        LocalDate today = LocalDate.now();
+        String dateStr = today.format(formatter);
+        List<Map> mapList1 = (List<Map>) formData.get(OPERATION_CONTROL.get("投资公司"));
+        List<Map> mapList2 = (List<Map>) formData.get(WORK_SAFETY.get("投资公司"));
+        List<Map> mapList3 = (List<Map>) formData.get(QUALITY_CONTROL.get("投资公司"));
+
+        final String[] content = {""};
+        if (mapList1.size()>0 && ObjectUtil.isNotNull(mapList1)){
+            content[0] = content[0] + "(一)运行控制 ";
+            mapList1.forEach(e->{
+                e.forEach((k,v)->{
+                    content[0] = content[0] + v;
+                });
+            });
+        }
+        if (mapList2.size()>0 && ObjectUtil.isNotNull(mapList2)) {
+            content[0] = content[0] + "(二)安全生产 ";
+            mapList2.forEach(e -> {
+                e.forEach((k, v) -> {
+                    content[0] = content[0] + v;
+                });
+            });
+        }
+        if (mapList3.size()>0 && ObjectUtil.isNotNull(mapList3)) {
+            content[0] = content[0] + "(三)质量控制 ";
+            mapList3.forEach(e -> {
+                e.forEach((k, v) -> {
+                    content[0] = content[0] + v;
+                });
+            });
+        }
+        if (mapList1.size()>0 || mapList2.size()>0 || mapList3.size()>0){
+            List<Map<String,Object>> records = new ArrayList<>();
+            Map<String, Object> map = UtilMap.map("fields", UtilMap.map("标题, 内容", "东航食品每日运行风险通告("+dateStr+")",content[0]));
+            records.add(map);
+            Map body = UtilMap.map("records", records);
+
+            try {
+                DDR.doPost("https://api.dingtalk.com/v1.0/notable/bases/XPwkYGxZV3yb9pLrsg2e2ZRZ8AgozOKL/sheets/数据表/records?operatorId=HFfiiOvDDoszFVOKmtiShJQwiEiE", DDConf.initTokenHeader(ddClient.getAccessToken()), null, body);
+            } catch (Exception e) {
+                log.info("返回值映射问题,不必理会");
+                e.printStackTrace();
+            }
+
+        }
+    }
+
+    @Override
+    public void getInvestmentCompany(boolean isLastTime) {
+        LocalDate today = LocalDate.now();
+        // 2. 定义常用格式化器(按需选择)
+        // 格式1:yyyy-MM-dd(推荐,通用格式)
+        String dateStr = today.format(formatter);
+//        List<Map> dataList = ydService.queryFormData_all(_initLYParam()
+//                .formUuid("FORM-609926407D9D4CABB771228200AEE57EKGSE")
+        List<Map> dataList = ydService.queryFormData_all(YDParam.builder()
+                .appType("APP_PUWIPZKD59N56D06GBJO")
+                .systemToken("KM666OC1PIGVI5674V8ZPAHRBQBD3LO0WQ5WKR1")
+                .formUuid("FORM-WY766581TT8BA0H9AYDQZ565KGDD2K9AX5SILH")
+                .createFromTimeGMT(dateStr + " 00:00:00").createToTimeGMT(dateStr + " 23:59:59")
+                .searchFieldJson(JSON.toJSONString(UtilMap.map("textField_mjjo82fr","否")))
+                .build());
+        log.info("日期:{},条数:{}",dateStr,dataList.size());
+        if (isLastTime || (ObjectUtil.isNotNull(dataList) && dataList.size() >= 3)){
+            Map<String, Object> formData = new HashMap<>();
+            formData.put("selectField_mjavj2ry","投资公司");
+            formData.put("dateField_mjb860nt",System.currentTimeMillis());
+            dataList.forEach(e->{
+                String type = UtilMap.getString(e,"textField_lis5zo49");
+                List<Map> sonList = (List<Map>) e.get("tableField_mj9dnvr7");
+                if (sonList.size() > 0 && ObjectUtil.isNotNull(sonList)){
+                    if("运行控制".equals(type)){
+                        sonList.forEach(s->{
+                            s.put("textareaField_mjavj2r7",UtilMap.getString(s,"textareaField_mj9dnvr8"));
+                        });
+                        formData.put("tableField_mjavj2r6",sonList);
+                    }else if ("安全生产".equals(type)) {
+                        sonList.forEach(s->{
+                            s.put("textareaField_mjgiipmy",UtilMap.getString(s,"textareaField_mj9dnvr8"));
+                        });
+                        formData.put("tableField_mjgiipn0",sonList);
+                    } else if ("质量控制".equals(type)) {
+                        sonList.forEach(s->{
+                            s.put("textareaField_mjgiipn2",UtilMap.getString(s,"textareaField_mj9dnvr8"));
+                        });
+                        formData.put("tableField_mjgiipn4",sonList);
+                    }
+                    ydClient.operateData(YDParam.builder()
+                            .appType("APP_PUWIPZKD59N56D06GBJO")
+                            .systemToken("KM666OC1PIGVI5674V8ZPAHRBQBD3LO0WQ5WKR1")
+                            .formInstanceId(UtilMap.getString(e, "formInstanceId"))
+                            .useLatestVersion(true)
+                            .updateFormDataJson(JSON.toJSONString(UtilMap.map("textField_mjjo82fr", "是")))
+                            .build(), YDConf.FORM_OPERATION.update);
+                }
+            });
+            ydClient.operateData(_initLYParam()
+                    .formUuid("FORM-609926407D9D4CABB771228200AEE57EKGSE")
+                    .formDataJson(JSON.toJSONString(formData))
+                    .build(), YDConf.FORM_OPERATION.start);
+        }
+    }
+
+    @Override
+    public void updateMeeting(String processInstanceId) {
+//        addToken( "6e16d9ac-104d-4c85-8d85-ee9d4295e2e5,0",1440);
+
+        log.info("回调执行更新缓存中计数数据:{}",processInstanceId);
+        String token = ddClient.getAccessToken(APP_EKY, APP_SECRET);
+        Map processData = ddClient_workflow.getProcessInstanceId(token, processInstanceId);
+        List<Map> formComponentValues = (List<Map>) processData.get("formComponentValues");
+        Map formComp = formComponentValues.stream().filter(item -> "宜搭班组会id".equals(item.get("name"))).findAny().get();
+        if (ObjectUtil.isNotNull(formComp)){
+            String value = formComp.get("value").toString();
+            // 流式判断:anyMatch 找到匹配项立即终止遍历
+            boolean hasStartWithS = formInstanceIdStore.keySet().stream()
+                    .filter(key -> key != null) // 过滤空 Key,避免 NPE
+                    .anyMatch(key -> key.startsWith(value));
+            log.info("是否查询到数据:{}",hasStartWithS);
+            // 若存在
+            if (hasStartWithS){
+                for (String key : formInstanceIdStore.keySet()) {
+                    // 避免空 Key 导致 NPE,先判空再匹配前缀
+                    if (key != null && key.startsWith(value)) {
+                        String[] split = key.split(",");
+                        int count = Integer.parseInt(split[1]);
+                        count++;
+                        formInstanceIdStore.put(split[0]+","+count, formInstanceIdStore.get(key));
+                        log.info("更新成功id:{},计数:{}",split[0],formInstanceIdStore.get(split[0]+"-"+count));
+                        formInstanceIdStore.remove(key);
+                        break; // 找到后立即退出循环,提升性能
+                    }
+                }
+            }
+
+        }
+    }
+
+    /**
+     * 创建视频会议
+     * @param userId
+     * @param title
+     * @return
+     */
+    @Override
+    public String createVideoMeeting(String userId, String title) {
+        String unionId = String.valueOf(ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), userId).get("unionid"));
+        MDR meeting = ddClientMeeting.createMeeting(ddClient.getAccessToken(), unionId, title, null, true);
+        return meeting.getExternalLinkUrl();
+    }
+
+
+    @Override
+    public void updateYidaMeeting() {
+        formInstanceIdStore.forEach((k, v) -> {
+            String[] split = k.split(",");
+            int count = Integer.parseInt(split[1]);
+            ydClient.operateData(_initLYParam()
+                    .formInstanceId(split[0])
+                    .useLatestVersion(true)
+                    .updateFormDataJson(JSON.toJSONString(UtilMap.map("numberField_mji9taov",count)))
+                    .build(), YDConf.FORM_OPERATION.update);
+            if (validateToken(k)) {
+                log.info("已处理流程id:{}",k);
+            }else {
+                log.info("已在缓存中移除流程id:{}",k);
+            }
+        });
+
+    }
+
+
+    @Override
+    public void testToken() {
+//        approveUpdateCRM(processInstanceId,"DXCPXS","审批拒绝", isAgree);
+    }
+
+    @Override
+    public void testGet(String id,boolean isAgree) {
+        System.out.println(id);
+        if (isAgree){
+            System.out.println("11111");
+            log.info("isAgree:{},",isAgree);
+        }
+        log.info("id:{},",id);
+    }
+
+
+
+}

+ 50 - 0
mjava-hangshi/src/main/java/com/malk/hangshi/service/PayService.java

@@ -0,0 +1,50 @@
+package com.malk.hangshi.service;
+
+import java.util.Map;
+
+public interface PayService {
+
+    /**
+     * 更新宜搭会议确定人数
+     */
+    void updateYidaMeeting();
+
+    void testToken();
+    void testGet(String id,boolean isAgree);
+
+    /**
+     * OA审批流程发起
+     */
+    Map<String, Object> startProcess(String formInstanceId, String depId, String serial);
+
+    /**
+     * 撤销OA审批流程
+     * @param data
+     * @return
+     */
+    Map terminateProcess(Map data);
+
+    /**
+     * 宜搭下发流程
+     * @param formInstanceId
+     */
+    void meetingIssue(String formInstanceId);
+
+    /**
+     * 获取投资公司班组会流程
+     */
+    void getInvestmentCompany(boolean isLastTime);
+
+    /**
+     * 更新班组会流程内确认人数
+     */
+    void updateMeeting(String processInstanceId);
+
+    /**
+     * 创建视频会议
+     * @param userId
+     * @param title
+     */
+    String createVideoMeeting(String userId, String title);
+
+}

+ 50 - 0
mjava-hangshi/src/main/resources/application-dev.yml

@@ -0,0 +1,50 @@
+# 环境配置
+server:
+  port: 8108
+  servlet:
+    context-path: /api/hangshi
+
+# condition
+spel:
+  scheduling: true        # 定时任务是否执行
+  multiSource: false       # 是否多数据源配置
+
+spring:
+  # database
+  datasource:
+    hikari:
+      connection-init-sql: SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci           # SqlServer, Oracle 无需设置类型
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    username: root
+    password: cp-root@2022++
+    url: jdbc:mysql://47.110.74.198:3306/dingtalk?serverTimezone=Asia/Shanghai&useUnicode=yes&characterEncoding=UTF-8&useSSL=true
+  jpa:
+    hibernate:
+      ddl-auto: none      # JPA对表没有任何操作
+    show-sql: true
+    database: MYSQL
+    database-platform: org.hibernate.dialect.MySQL57Dialect
+
+# filepath
+file:
+  path:
+    file: /Users/malk/server/_Tool/var/mjava/tmp/file/
+    image: /Users/malk/server/_Tool/var/mjava/tmp/image/
+    tmp: /Users/malk/server/_Tool/var/mjava/tmp/
+  source:
+    fonts: /Users/malk/server/_Tool/fonts/simsun.ttc
+#logging:
+#  file:
+#    path: /Users/malk/server/_Tool/var/mjava/log
+
+# dingtalk
+dingtalk:
+  agentId: 4129565358
+  appKey: dingcjcm9hy27nks2m6v
+  appSecret: 3U0K9HwpxIRv5lDD2-dR2L5ABr-fcZGESEgZrRmsKdl5nfiZusHTXefdOyC5-0VO
+  corpId:
+  aesKey: 2MVNZx7j4vK9wVg7VryKTiRjrIf1cQoItbfuZaXYFAs
+  token: IBmyTdgXNs7I9L4dPrx0FbFyC4ZfBrA6d7tBeKFd2ph7EuUD9Sc9tHI
+  operator: ""   # OA管理员账号
+
+

+ 39 - 0
mjava-hangshi/src/main/resources/application-prod.yml

@@ -0,0 +1,39 @@
+# 环境配置
+server:
+  port: 8108
+  servlet:
+    context-path: /api/hangshi
+
+# condition
+spel:
+  scheduling: true        # 定时任务是否执行
+  multiSource: false       # 是否多数据源配置
+
+spring:
+  # database
+  datasource:
+    hikari:
+      connection-init-sql: SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci           # SqlServer, Oracle 无需设置类型
+    driver-class-name: com.mysql.cj.jdbc.Driver
+#    username: root
+#    password: cp-root@2022++
+#    url: jdbc:mysql://127.0.0.1:3306/dingtalk?serverTimezone=Asia/Shanghai&useUnicode=yes&characterEncoding=UTF-8&useSSL=true
+    username: root
+    password: cp-root@2022++
+    url: jdbc:mysql://47.110.74.198:3306/dingtalk?serverTimezone=Asia/Shanghai&useUnicode=yes&characterEncoding=UTF-8&useSSL=true
+#  jpa:
+#    hibernate:
+#      ddl-auto: none      # JPA对表没有任何操作
+#    show-sql: true
+#    database: MYSQL
+#    database-platform: org.hibernate.dialect.MySQL57Dialect
+
+# dingtalk
+dingtalk:
+  agentId: 4129565358
+  appKey: dingcjcm9hy27nks2m6v
+  appSecret: 3U0K9HwpxIRv5lDD2-dR2L5ABr-fcZGESEgZrRmsKdl5nfiZusHTXefdOyC5-0VO
+  corpId:
+  aesKey: 2MVNZx7j4vK9wVg7VryKTiRjrIf1cQoItbfuZaXYFAs
+  token: IBmyTdgXNs7I9L4dPrx0FbFyC4ZfBrA6d7tBeKFd2ph7EuUD9Sc9tHI
+  operator: ""   # OA管理员账号

+ 43 - 0
mjava-hangshi/src/test/java/test.java

@@ -0,0 +1,43 @@
+import com.malk.hangshi.Boot;
+import com.malk.hangshi.service.Impl.PayServiceImpl;
+import com.malk.server.dingtalk.DDConf;
+import com.malk.service.dingtalk.DDClient;
+import com.malk.service.dingtalk.DDClient_Event;
+import com.malk.service.dingtalk.DDClient_Workflow;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.util.Map;
+
+@Slf4j
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = Boot.class)
+public class test {
+
+    @Autowired
+    private DDConf ddConf;
+    @Autowired
+    private DDClient_Event ddClient_event;
+    @Autowired
+    private DDClient ddClient;
+    @Autowired
+    private PayServiceImpl payService;
+    @Autowired
+    private DDClient_Workflow ddClient_workflow;
+    @Value("${dingtalk.appKey}")
+    private String APP_EKY;
+    @Value("${dingtalk.appSecret}")
+    private String APP_SECRET;
+    @Test
+    public void sonUpdateTest(){
+
+        Map processData = ddClient_workflow.getProcessInstanceId(ddClient.getAccessToken(APP_EKY, APP_SECRET), "Zd7HDIdGTkiGFFtsoHkg3A03891763390051");
+
+        System.out.println(processData);
+    }
+}

+ 448 - 0
pom.xml

@@ -0,0 +1,448 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>com.malk</groupId>
+    <artifactId>java-mcli</artifactId>
+    <version>1.0-SNAPSHOT</version>
+
+    <modules>
+        <module>mjava-hangshi</module>
+    </modules>
+    <packaging>pom</packaging>
+
+    <name>java-mcli</name>
+    <description>mjava framework</description>
+
+    <!-- 版本管理 Management -->
+    <properties>
+        <!-- mjava版本: 修改mjava pom配置 -->
+        <mjava.version>0.0.3</mjava.version>
+        <!-- 全局配置 -->
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <java.version>1.8</java.version>
+        <!-- 公共依赖 -->
+        <spring-boot-dependencies.version>2.2.13.RELEASE</spring-boot-dependencies.version>
+        <junit.verson>4.12</junit.verson>
+        <lombok.version>1.18.8</lombok.version>
+        <validation-api.version>2.0.1.Final</validation-api.version>
+        <fastjson.version>1.2.83</fastjson.version>
+        <commons-lang3.version>3.10</commons-lang3.version>
+        <guava.version>30.1.1-jre</guava.version>
+        <hutool-all.version>5.6.0</hutool-all.version>
+        <spring-boot-starter-data-jpa.version>2.1.3.RELEASE</spring-boot-starter-data-jpa.version>
+        <querydsl-apt.version>4.2.1</querydsl-apt.version>
+        <querydsl-jpa.version>4.2.1</querydsl-jpa.version>
+        <spring-boot-starter-jdbc.version>2.2.13.RELEASE</spring-boot-starter-jdbc.version>
+        <easyexcel.version>2.2.7</easyexcel.version>
+        <!-- 数据库连接 [仅mysql为全局依赖] -->
+        <mysql-connector-java.version>8.0.22</mysql-connector-java.version>
+        <mssql-jdbc.version>6.4.0.jre8</mssql-jdbc.version>
+        <ojdbc6.version>11.2.0.4</ojdbc6.version>
+        <mongo-java-driver.version>3.12.7</mongo-java-driver.version>
+        <spring-boot-starter-data-mongodb.version>2.2.13.RELEASE</spring-boot-starter-data-mongodb.version>
+        <!-- jsp [非全局依赖] -->
+        <tomcat-embed-jasper.version>9.0.41</tomcat-embed-jasper.version>
+        <jstl.version>1.2</jstl.version>
+        <javax.servlet-api.version>4.0.1</javax.servlet-api.version>
+        <javax.servlet.jsp-api.version>2.3.1</javax.servlet.jsp-api.version>
+        <!-- jwt [非全局依赖] -->
+        <java-jwt.version>3.4.0</java-jwt.version>
+        <!-- swagger3: todo -->
+        <springfox-boot-starter.version>3.0.0</springfox-boot-starter.version>
+        <!-- 网页转pdf [非全局依赖] -->
+        <flying-saucer-pdf-itext5.version>9.0.3</flying-saucer-pdf-itext5.version>
+        <!-- 腾讯云[发票识别] [非全局依赖] -->
+        <tencentcloud-sdk-java.version>3.1.778</tencentcloud-sdk-java.version>
+        <!-- 不执行单元测试,也不编译测试类 -->
+        <skipTests>true</skipTests>
+        <!-- 不执行单元测试,但会编译测试类,并在target/test-classes目录下生成相应的class -->
+        <maven.test.skip>true</maven.test.skip>
+        <mybatis-plus.version>3.5.1</mybatis-plus.version>
+        <durid.version>1.1.18</durid.version>
+    </properties>
+
+    <!-- 依赖声明 & 版本 -->
+    <dependencyManagement>
+        <dependencies>
+            <!-- SpringBoot 依赖 -->
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring-boot-dependencies.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <!-- 单元测试 -->
+            <dependency>
+                <groupId>junit</groupId>
+                <artifactId>junit</artifactId>
+                <version>${junit.verson}</version>
+                <scope>test</scope>
+            </dependency>
+
+            <!-- lombok -->
+            <dependency>
+                <groupId>org.projectlombok</groupId>
+                <artifactId>lombok</artifactId>
+                <version>${lombok.version}</version>
+                <scope>provided</scope>
+            </dependency>
+
+            <!-- validation 参数校验 -->
+            <dependency>
+                <groupId>javax.validation</groupId>
+                <artifactId>validation-api</artifactId>
+                <version>${validation-api.version}</version>
+            </dependency>
+
+            <!-- 阿里巴巴 json -->
+            <dependency>
+                <groupId>com.alibaba</groupId>
+                <artifactId>fastjson</artifactId>
+                <version>${fastjson.version}</version>
+            </dependency>
+
+            <!-- 通用的工具类集 -->
+            <dependency>
+                <groupId>org.apache.commons</groupId>
+                <artifactId>commons-lang3</artifactId>
+                <version>${commons-lang3.version}</version>
+            </dependency>
+            <!-- ppExt: 23.10.26 钉钉新方式以Steam接入, HTTP形式commonsc-codec在升级之后,其内部做了一个validateCharacter校验. 使用 guava 替代-->
+            <dependency>
+                <groupId>com.google.guava</groupId>
+                <artifactId>guava</artifactId>
+                <version>${guava.version}</version>
+            </dependency>
+            <!-- 国产工具集 -->
+            <dependency>
+                <groupId>cn.hutool</groupId>
+                <artifactId>hutool-all</artifactId>
+                <version>${hutool-all.version}</version>
+            </dependency>
+
+            <!-- data-jpa 数据库操作 -->
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-starter-data-jpa</artifactId>
+                <version>${spring-boot-starter-data-jpa.version}</version>
+            </dependency>
+
+            <!-- QueryDSL 4.x 支持-->
+            <dependency>
+                <groupId>com.querydsl</groupId>
+                <artifactId>querydsl-apt</artifactId>
+                <scope>provided</scope>
+                <version>${querydsl-apt.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.querydsl</groupId>
+                <artifactId>querydsl-jpa</artifactId>
+                <version>${querydsl-jpa.version}</version>
+            </dependency>
+
+            <!-- AOP多数据源切换 -->
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-starter-jdbc</artifactId>
+                <version>${spring-boot-starter-jdbc.version}</version>
+            </dependency>
+
+            <!-- easyExcel 优化 poi [不影响单独引入poi, 会冲突] -->
+            <dependency>
+                <groupId>com.alibaba</groupId>
+                <artifactId>easyexcel</artifactId>
+                <version>${easyexcel.version}</version>
+            </dependency>
+
+            <!-- mySql 驱动 -->
+            <dependency>
+                <groupId>mysql</groupId>
+                <artifactId>mysql-connector-java</artifactId>
+                <version>${mysql-connector-java.version}</version>
+            </dependency>
+
+            <!-- sqlserver依赖 -->
+            <dependency>
+                <groupId>com.microsoft.sqlserver</groupId>
+                <artifactId>mssql-jdbc</artifactId>
+                <scope>runtime</scope>
+                <version>${mssql-jdbc.version}</version>
+            </dependency>
+
+            <!-- Oracle 依赖 -->
+            <dependency>
+                <groupId>com.oracle.database.jdbc</groupId>
+                <artifactId>ojdbc6</artifactId>
+                <version>${ojdbc6.version}</version>
+            </dependency>
+
+            <!-- MongoDB 驱动 -->
+            <dependency>
+                <groupId>org.mongodb</groupId>
+                <artifactId>mongo-java-driver</artifactId>
+                <version>${mongo-java-driver.version}</version>
+            </dependency>
+            <!-- MongoDB jpa 操作 -->
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-starter-data-mongodb</artifactId>
+                <version>${spring-boot-starter-data-mongodb.version}</version>
+            </dependency>
+
+            <!-- url转pdf -->
+            <dependency>
+                <groupId>org.xhtmlrenderer</groupId>
+                <artifactId>flying-saucer-pdf-itext5</artifactId>
+                <version>${flying-saucer-pdf-itext5.version}</version>
+            </dependency>
+
+            <!-- jsp: tomcat-embed-jasper 需要添加到子项目内 -->
+            <dependency>
+                <groupId>org.apache.tomcat.embed</groupId>
+                <artifactId>tomcat-embed-jasper</artifactId>
+                <version>${tomcat-embed-jasper.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>javax.servlet</groupId>
+                <artifactId>jstl</artifactId>
+                <version>${jstl.version}</version>
+            </dependency>
+            <!-- servlet -->
+            <dependency>
+                <groupId>javax.servlet</groupId>
+                <artifactId>javax.servlet-api</artifactId>
+                <version>${javax.servlet-api.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>javax.servlet.jsp</groupId>
+                <artifactId>javax.servlet.jsp-api</artifactId>
+                <version>${javax.servlet.jsp-api.version}</version>
+            </dependency>
+
+            <!-- jwt -->
+            <dependency>
+                <groupId>com.auth0</groupId>
+                <artifactId>java-jwt</artifactId>
+                <version>${java-jwt.version}</version>
+            </dependency>
+
+            <!-- 腾讯云 -->
+            <dependency>
+                <groupId>com.tencentcloudapi</groupId>
+                <artifactId>tencentcloud-sdk-java</artifactId>
+                <version>${tencentcloud-sdk-java.version}</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <!-- 子项目需要与 mjava 相同的依赖, 否则调试可运行, 打包后会运行报错. 为了避免重复引入 pom, 将 mjava 依赖直接在全局 pom 引入 -->
+    <dependencies>
+        <!-- spring boot -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-tomcat</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <!-- spring-boot-devtools [热部署] -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-devtools</artifactId>
+            <optional>true</optional> <!-- 表示依赖不会传递 -->
+        </dependency>
+        <!-- 单元测试 -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <!-- lombok -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- validation 参数校验 -->
+        <dependency>
+            <groupId>javax.validation</groupId>
+            <artifactId>validation-api</artifactId>
+        </dependency>
+
+        <!-- 阿里巴巴 json -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+        </dependency>
+
+        <!-- 通用的工具类集 -->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+        <!-- 国产工具集 -->
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+        </dependency>
+
+        <!-- data-jpa 数据库操作 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-jpa</artifactId>
+        </dependency>
+
+        <!-- QueryDSL 4.x 支持-->
+        <dependency>
+            <groupId>com.querydsl</groupId>
+            <artifactId>querydsl-apt</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.querydsl</groupId>
+            <artifactId>querydsl-jpa</artifactId>
+        </dependency>
+
+        <!-- AOP多数据源切换 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-jdbc</artifactId>
+        </dependency>
+
+        <!-- easyExcel 优化 poi [不影响单独引入poi, 会冲突] -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>easyexcel</artifactId>
+        </dependency>
+
+        <!-- mySql 驱动 -->
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
+        <!--        引入mybatisPlus 包含了 jdbc -->
+<!--        <dependency>-->
+<!--            <groupId>com.baomidou</groupId>-->
+<!--            <artifactId>mybatis-plus-boot-starter</artifactId>-->
+<!--            <version>${mybatis-plus.version}</version>-->
+<!--        </dependency>-->
+
+        <!--        引入durid數據源-->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid-spring-boot-starter</artifactId>
+            <version>${durid.version}</version>
+        </dependency>
+
+        <!-- Oracle 依赖 -->
+<!--                <dependency>-->
+<!--                    <groupId>com.oracle.database.jdbc</groupId>-->
+<!--                    <artifactId>ojdbc6</artifactId>-->
+<!--                </dependency>-->
+
+<!--         sqlserver 依赖 -->
+<!--                <dependency>-->
+<!--                    <groupId>com.microsoft.sqlserver</groupId>-->
+<!--                    <artifactId>mssql-jdbc</artifactId>-->
+<!--                    <scope>runtime</scope>-->
+<!--                </dependency>-->
+
+
+<!--         MongoDB 驱动 -->
+<!--                <dependency>-->
+<!--                    <groupId>org.mongodb</groupId>-->
+<!--                    <artifactId>mongo-java-driver</artifactId>-->
+<!--                </dependency>-->
+<!--         MongoDB jpa 操作 -->
+<!--                <dependency>-->
+<!--                    <groupId>org.springframework.boot</groupId>-->
+<!--                    <artifactId>spring-boot-starter-data-mongodb</artifactId>-->
+<!--                    <version>${spring-boot-starter-data-mongodb.version}</version>-->
+<!--                </dependency>-->
+
+<!--         url转pdf -->
+<!--                <dependency>-->
+<!--                    <groupId>org.xhtmlrenderer</groupId>-->
+<!--                    <artifactId>flying-saucer-pdf-itext5</artifactId>-->
+<!--                </dependency>-->
+
+<!--         jsp: tomcat-embed-jasper 需要在子项目内引用才有效 -->
+<!--                <dependency>-->
+<!--                    <groupId>javax.servlet</groupId>-->
+<!--                    <artifactId>jstl</artifactId>-->
+<!--                </dependency>-->
+<!--         servlet -->
+                <dependency>
+                    <groupId>javax.servlet</groupId>
+                    <artifactId>javax.servlet-api</artifactId>
+                </dependency>
+                <dependency>
+                    <groupId>javax.servlet.jsp</groupId>
+                    <artifactId>javax.servlet.jsp-api</artifactId>
+                </dependency>
+
+<!--         jwt -->
+                <dependency>
+                    <groupId>com.auth0</groupId>
+                    <artifactId>java-jwt</artifactId>
+                </dependency>
+
+<!--         腾讯云 [go to https://search.maven.org/search?q=tencentcloud-sdk-java and get the latest version.] -->
+<!--                <dependency>-->
+<!--                    <groupId>com.tencentcloudapi</groupId>-->
+<!--                    <artifactId>tencentcloud-sdk-java</artifactId>-->
+<!--                </dependency>-->
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.8.1</version>
+                <configuration>
+                    <source>${java.version}</source>
+                    <target>${java.version}</target>
+                    <encoding>${project.build.sourceEncoding}</encoding>
+                    <!-- jva使用了未经检查或不安全的操作,编译会有打印,通过插件显示具体报错警告位置, 如 T, Map, List 也会报警告 -->
+                    <compilerArgument>-Xlint:unchecked</compilerArgument>
+                </configuration>
+            </plugin>
+            <!-- QueryDSL 插件: 因为QueryDsl是类型安全的,所以还需要加上Maven APT plugin,使用 APT 自动生成Q类 -->
+            <plugin>
+                <groupId>com.mysema.maven</groupId>
+                <artifactId>apt-maven-plugin</artifactId>
+                <version>1.1.3</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>process</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>target/generated-sources/java</outputDirectory>
+                            <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>