活动介绍
file-type

Android开发中的OkHttp网络请求header缺失问题解决工具

RAR文件

下载需积分: 9 | 2KB | 更新于2024-12-25 | 22 浏览量 | 0 下载量 举报 收藏
download 立即下载
在网络开发中,特别是在Android平台,网络请求是应用与服务器交互的重要手段。为了实现网络请求,开发者们往往会选择合适的网络库来简化开发过程。在本资源中,我们主要讨论的是关于网络请求封装工具类,并且提到了一个关键点——缺少header。 ### 知识点一:网络请求封装工具类的重要性 封装网络请求工具类的原因主要是为了代码复用和模块化。在Android开发中,网络请求往往需要处理多种情况,如登录认证、参数传递、异常处理等。通过封装网络请求工具类,我们可以将这些通用逻辑封装起来,以供不同的业务模块调用。这样的封装不仅可以提高代码的复用性,还可以让代码结构更清晰,便于维护和管理。 ### 知识点二:header的作用与重要性 Header(头部信息)在网络请求中起着至关重要的作用。它是HTTP请求的一部分,提供了关于请求和响应的元数据信息,比如: - 认证信息(如API密钥或令牌) - 内容类型(如application/json或application/xml) - 缓存控制(如max-age或no-cache) - 请求优先级(如优先级头信息) 缺少header可能导致多种问题,比如: - 服务端认证失败,无法识别请求方身份 - 内容类型不匹配导致的解析错误 - 缓存策略导致的错误数据读取 - 请求优先级未设置导致的资源浪费 ### 知识点三:Android中使用OkHttp库进行网络请求封装 OkHttp是一个强大且高效、支持多种协议的HTTP客户端库。它被广泛用于Android开发中以处理HTTP请求。使用OkHttp封装网络请求,可以按照以下步骤进行: 1. 添加依赖:首先需要在项目的build.gradle文件中添加OkHttp的依赖。 ```gradle dependencies { implementation 'com.squareup.okhttp3:okhttp:4.9.0' } ``` 2. 创建OkHttpClient实例:OkHttpClient用于创建和管理请求的底层网络连接。 ```java OkHttpClient client = new OkHttpClient(); ``` 3. 构建Request对象:使用Request.Builder来创建Request对象,可以设置请求的URL、方法(GET、POST等)、headers和body。 ```java Request request = new Request.Builder() .url("https://api.example.com/data") .addHeader("Authorization", "Bearer your_token_here") .addHeader("Content-Type", "application/json") .build(); ``` 4. 创建并执行Call对象:通过OkHttpClient发起请求,返回一个Call对象,然后可以同步或异步地执行该请求。 ```java Call call = client.newCall(request); // 同步执行 Response response = call.execute(); // 异步执行 call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { // 请求失败处理 } @Override public void onResponse(Call call, Response response) throws IOException { // 请求成功处理 } }); ``` 5. 处理响应:通过Response对象可以获取到服务器返回的数据以及相关的元数据信息。 ### 知识点四:Header的设置与管理 在OkHttp中设置header是非常直接的,如前面代码所示,使用addHeader方法添加到Request.Builder中。良好的header管理应遵循以下准则: - 确保每次请求都包含必要的header,如认证信息。 - 避免在请求中包含敏感信息,除非必要且加密。 - 根据需要合理使用cache-control header以优化网络性能。 - 使用合适的Content-Type header确保服务端正确解析请求体。 ### 知识点五:网络请求封装工具类的设计与实现 网络请求封装工具类的设计与实现需要注意以下几点: - **接口化**:定义统一的请求接口,便于扩展和替换。 - **异常处理**:合理的异常捕获和处理机制,提升用户体验。 - **多环境适配**:考虑不同环境(开发、测试、生产)下的配置差异。 - **数据解析**:封装工具类应该支持多种数据格式的解析和转换。 - **链式调用**:提供链式调用的设计,让代码编写更加流畅。 综上所述,通过使用OkHttp在Android中封装网络请求工具类,可以有效地解决请求发送和响应处理过程中可能遇到的问题,提高开发效率和应用性能。同时,对于header的重视和正确设置对于确保请求的正确性和安全性至关重要。

相关推荐

filetype

@POST @Path("/registInfo") @Consumes(MediaType.MULTIPART_FORM_DATA) public AjaxResult registInfo( @FormDataParam("file") InputStream fileInputStream, @FormDataParam("file") FormDataContentDisposition fileMeta, @FormDataParam("workCode") String workCode ) throws IOException { // 参数校验 if (fileInputStream == null || fileMeta == null || fileMeta.getFileName() == null) { return AjaxResult.error("文件不能为空"); } if (workCode == null || workCode.trim().isEmpty()) { return AjaxResult.error("工号不能为空"); } try (InputStream inputStream = fileInputStream; ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } byte[] fileBytes = outputStream.toByteArray(); Map<String, Object> map = new HashMap<>(); map.put("file", fileBytes); map.put("fileName", fileMeta.getFileName()); map.put("workCode", workCode); return assetManagementService().registInfo(map); } catch (IOException e) { return AjaxResult.error("文件读取失败: " + e.getMessage()); } catch (Exception e) { return AjaxResult.error("处理失败: " + e.getMessage()); } }public class AssetManagementImpl extends Service implements AssetManagementService { private final Logger logger = LoggerFactory.getLogger(AssetManagementImpl.class); private RequestInfo request; public RequestInfo getRequest(){ return request; } public void setRequest(RequestInfo request) { this.request = request; } @Override public AjaxResult registInfo(Map<String, Object> data) { logger.info("参数: " + data); // 1. 参数校验 String workCode = data.get("workCode").toString(); if (workCode == null || workCode.isEmpty()) { return AjaxResult.error("工号(workCode)不能为空"); } logger.info("附件10: "+workCode); // 2. 处理文件参数 File tempFile = null; try { MultipartFile multipartFile = null; if (data.get("file") instanceof MultipartFile) { multipartFile = (MultipartFile) data.get("file"); } else { return AjaxResult.error("不支持的文件类型"); } logger.info("附件11: "+data.get("file")); // 3. 创建临时文件 tempFile = File.createTempFile( "upload-", multipartFile.getOriginalFilename(), new File(System.getProperty("java.io.tmpdir")) ); multipartFile.transferTo(tempFile); logger.info("附件12: "+tempFile); // 4. 认证流程 String basePath = "http://192.168.1.161"; try (CloseableHttpClient httpClient = HttpClients.createDefault()) { JSONObject authResponse = authenticate(httpClient, basePath + "/api/ec/dev/auth/regist"); if (!authResponse.has("secret") || !authResponse.has("spk")) { return AjaxResult.error("认证失败: 缺少必要字段"); } logger.info("附件13: "+authResponse); String token = getToken(httpClient, basePath + "/api/ec/dev/auth/applytoken", rsaEncrypt(authResponse.getString("secret"), authResponse.getString("spk"))); logger.info("附件14: "+token); // 5. 获取人员ID并加密 String personnelId = getPersonnelId(httpClient, basePath + "/api/initiate/staffId", workCode); if (personnelId == null) { return AjaxResult.error("获取人员ID失败"); } String encryptedUserId = rsaEncrypt(personnelId, authResponse.getString("spk")); logger.info("附件15: "+encryptedUserId); // 6. 调用文件上传 JSONObject uploadResult = uploadFileToOa( httpClient, basePath + "/api/doc/upload/uploadFile2Doc", token, encryptedUserId, tempFile, multipartFile.getOriginalFilename() ); logger.info("附件17: "+uploadResult); // 7. 处理返回结果 if (uploadResult.has("data") && uploadResult.getJSONObject("data").has("fileid")) { return AjaxResult.success("上传成功", uploadResult.getJSONObject("data")); } else { return AjaxResult.error("上传失败: " + uploadResult); } } } catch (Exception e) { logger.error("文件上传异常: ", e); return AjaxResult.error("服务器错误: " + e.getMessage()); } finally { // 8. 清理临时文件 if (tempFile != null && tempFile.exists()) { tempFile.delete(); } } } private JSONObject uploadFileToOa( CloseableHttpClient httpClient, String url, String token, String encryptedUserId, File file, String originalFilename ) throws Exception { HttpPost httpPost = new HttpPost(url); logger.info("附件1: "+token +": "+ file +": "+ originalFilename); // 1. 设置请求头 httpPost.setHeader("appid", "register"); httpPost.setHeader("token", token); httpPost.setHeader("userid", encryptedUserId); logger.info("附件2: "+token +": "+ file +": "+ encryptedUserId); // 2. 构建Multipart请求体 MultipartEntityBuilder builder = MultipartEntityBuilder.create() .setMode(HttpMultipartMode.RFC6532) .addBinaryBody("file", file, ContentType.MULTIPART_FORM_DATA, originalFilename) .addTextBody("name", originalFilename, ContentType.TEXT_PLAIN) .addTextBody("category", "10", ContentType.create("text/plain", StandardCharsets.UTF_8)); logger.info("附件3: "+token +": "+ file +": "+ encryptedUserId); httpPost.setEntity(builder.build()); logger.info("附件4: "+token +": "+ file +": "+ encryptedUserId); // 3. 执行请求并解析响应 try (CloseableHttpResponse response = httpClient.execute(httpPost)) { String responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); logger.info("附件5: "+responseBody); return new JSONObject(responseBody); } } private JSONObject authenticate(CloseableHttpClient client, String url) throws Exception { HttpPost request = new HttpPost(url); request.setHeader("appid", "register"); request.setHeader("cpk", "123"); try (CloseableHttpResponse response = client.execute(request)) { int statusCode = response.getStatusLine().getStatusCode(); if (statusCode != 200) { throw new Exception("认证失败,状态码: " + statusCode); } String responseBody = EntityUtils.toString(response.getEntity()); JSONObject json = new JSONObject(responseBody); if (!json.has("secret") || !json.has("spk")) { throw new Exception("认证响应缺少必要字段: " + responseBody); } return json; } } private String getToken(CloseableHttpClient client, String url, String encryptedSecret) throws Exception { HttpPost request = new HttpPost(url); request.setHeader("appid", "register"); request.setHeader("secret", encryptedSecret); try (CloseableHttpResponse response = client.execute(request)) { JSONObject json = new JSONObject(EntityUtils.toString(response.getEntity())); return json.getString("token"); } } private String getPersonnelId(CloseableHttpClient client, String url, String workCode) throws Exception { HttpPost request = new HttpPost(url); request.setHeader("Content-Type", "application/json"); JSONObject params = new JSONObject(); params.put("workCode", workCode); request.setEntity(new StringEntity(params.toString())); try (CloseableHttpResponse response = client.execute(request)) { JSONObject json = new JSONObject(EntityUtils.toString(response.getEntity())); return json.optString("personnelId", null); } } public String rsaEncrypt(String data, String publicKeyStr) throws Exception { publicKeyStr = publicKeyStr.trim() .replaceAll("-----BEGIN[^-]+-----", "") .replaceAll("-----END[^-]+-----", "") .replaceAll("\\s", ""); while (publicKeyStr.length() % 4 != 0) { publicKeyStr += "="; } if (!publicKeyStr.matches("[A-Za-z0-9+/=]+")) { throw new IllegalArgumentException("公钥包含非法Base64字符"); } String pemKey = "-----BEGIN PUBLIC KEY-----\n" + formatBase64Blocks(publicKeyStr) + "\n" + "-----END PUBLIC KEY-----"; try (StringReader reader = new StringReader(pemKey); PEMParser parser = new PEMParser(reader)) { Object parsed = parser.readObject(); if (!(parsed instanceof SubjectPublicKeyInfo)) { throw new IOException("不是有效的公钥PEM格式"); } PublicKey publicKey = new JcaPEMKeyConverter().getPublicKey((SubjectPublicKeyInfo) parsed); Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); cipher.init(Cipher.ENCRYPT_MODE, publicKey); byte[] encrypted = cipher.doFinal(data.getBytes("UTF-8")); return Base64.encodeBase64String(encrypted); } } private String formatBase64Blocks(String data) { return data.replaceAll("(.{64})", "$1\n"); } }上传请求接口报415的错误

filetype

/** * 微信小程序数据管理器 * 基于DataManager.js的逻辑,但适配微信小程序环境 */ // 解析数据引用关系的辅助函数 /** * 解析数据中的引用关系 * * 该函数用于处理嵌套的数据结构,将数据中的引用关系解析为实际的对象引用。 * 它会遍历数据中的所有实体,查找属性名中包含的数字(如"item1"), * 并尝试将这些属性值替换为对应类型数据中的实际对象引用。 * * @param {Object} data - 包含嵌套引用的原始数据对象 * @returns {Object} 处理后的数据对象,其中引用已被解析为实际对象 */ function resolveDataReferences(data) { const keys = Object.keys(data); for (const key of keys) { const entities = data[key]; for (const entity of entities) { for (const attribute in entity) { if (entity.hasOwnProperty(attribute)) { // 修复:统一使用复数形式查找引用类型 let refType = attribute.replace(/\d/g, ''); // 尝试直接查找复数形式 if (!data[refType] && data[`${refType}s`]) { refType = `${refType}s`; } if (Array.isArray(entity[attribute])) { entity[attribute] = entity[attribute].map(item => data[refType]?.find(updateItem => updateItem.id === item.id) || item ); } else if (typeof entity[attribute] === "object" && entity[attribute] !== null) { entity[attribute] = data[refType]?.find(updateItem => updateItem.id === entity[attribute].id) || entity[attribute]; } } } } } return data; } // 解析单个实体的数据引用 /** * 解析数据引用关系 * * 该函数用于处理实体对象与数据源之间的引用关系,自动匹配并更新实体中的引用字段。 * * @param {Object} entity - 需要处理的实体对象 * @param {Object} data - 包含引用数据的数据源对象 * @returns {Object} 处理后的实体对象 * * 功能说明: * 1. 遍历实体对象的每个属性 * 2. 如果属性值是数组,则尝试在数据源中查找匹配项更新数组元素 * 3. 如果属性值是对象,则尝试在数据源中查找匹配项更新该对象 * 4. 自动处理单复数形式的数据源键名 */ function resolveDataReference(entity, data) { for (const attribute in entity) { if (entity.hasOwnProperty(attribute)) { // 修复:统一使用复数形式查找引用类型 let refType = attribute.replace(/\d/g, ''); // 尝试直接查找复数形式 if (!data[refType] && data[`${refType}s`]) { refType = `${refType}s`; } if (Array.isArray(entity[attribute])) { entity[attribute] = entity[attribute].map(item => data[refType]?.find(updateItem => updateItem.id === item.id) || item ); } else if (typeof entity[attribute] === "object" && entity[attribute] !== null) { entity[attribute] = data[refType]?.find(updateItem => updateItem.id === entity[attribute].id) || entity[attribute]; } } } return entity; } /** * 微信小程序数据管理器类 * 负责与后端API通信并管理数据,适配微信小程序环境 */ /** * 小程序数据管理类 * * 该类负责管理小程序的所有数据,包括网络数据和本地数据,并提供数据同步、CRUD操作等功能。 * * @property {Object} networkData - 从网络获取的数据 * @property {Object} localData - 本地存储的数据 * @property {Object} data - 合并后的数据(本地数据优先) * @property {boolean} isSyncing - 是否正在同步数据 * @property {Date} lastSync - 最后同步时间 * @property {Object} callbacks - 回调函数集合 * @property {Promise} syncQueue - 同步队列 * @property {Object} entiyeText - 实体重复提示文本 * @property {number} syncInterval - 自动同步间隔(毫秒) * @property {string} storageKey - 本地存储键名 * * @param {string} baseUrl - API基础URL * * 功能包括: * - 数据自动同步 * - 本地数据持久化 * - 数据合并策略 * - 实体CRUD操作 * - 重复检查 * - 回调机制 */ /** * 懒加载处理器 * 负责管理实体类之间的关联依赖,实现按需加载 */ class LazyLoader { constructor(dataManager) { this.dataManager = dataManager; this.cache = new Map(); } /** * 创建实体代理 * @param {Object} entity 实体对象 * @param {string} entityType 实体类型 * @returns {Proxy} 返回代理后的实体 */ createProxy(entity, entityType) { const handler = { get: (target, prop) => { // 如果是普通属性,直接返回 if (typeof target[prop] !== 'object' || target[prop] === null) { return target[prop]; } // 如果是关联对象,懒加载 if (prop.endsWith('_ref')) { const refType = prop.replace('_ref', ''); return this.loadReference(target[prop], refType); } // 如果是关联数组,懒加载 if (prop.endsWith('_refs')) { const refType = prop.replace('_refs', ''); return this.loadReferences(target[prop], refType); } return target[prop]; } }; return new Proxy(entity, handler); } /** * 加载单个关联引用 */ async loadReference(ref, refType) { if (!ref || !ref.id) return null; const cacheKey = `${refType}_${ref.id}`; if (this.cache.has(cacheKey)) { return this.cache.get(cacheKey); } const entities = this.dataManager.data[`${refType}s`] || []; let entity = entities.find(e => e.id === ref.id); if (!entity) { await this.dataManager.syncData(); entity = (this.dataManager.data[`${refType}s`] || []) .find(e => e.id === ref.id); } if (entity) { const proxy = this.createProxy(entity, refType); this.cache.set(cacheKey, proxy); return proxy; } return null; } /** * 加载多个关联引用 */ async loadReferences(refs, refType) { if (!Array.isArray(refs)) return []; return Promise.all( refs.map(ref => this.loadReference(ref, refType)) ); } } class MiniProgramDataManager { // 修复:合并重复的构造函数 constructor(baseUrl) { this.baseUrl = baseUrl; this.debug = true; // 调试模式开关 this.requestCount = 0; // 请求计数器 // 数据结构定义 this._rawData = { bancais: [], dingdans: [], mupis: [], chanpins: [], kucuns: [], dingdan_bancais: [], chanpin_zujians: [], zujians: [], caizhis: [], dingdan_chanpins: [], users: [], jinhuos: [], _lastModified: null, _lastSync: null }; // 初始化网络状态 this.networkAvailable = false; this.checkNetwork().then(type => { this.networkAvailable = type !== 'none'; }); this.lazyLoader = new LazyLoader(this); // 仅从本地存储加载数据,不自动同步 this.loadDataFromStorage(); this.isSyncing = false; this.lastSync = null; this.callbacks = { all: [], bancais: [], dingdan: [], mupi: [], chanpin: [], kucun: [], chanpin_zujian: [], dingdan_bancai: [], zujian: [], caizhi: [], dingdan_chanpin: [], user: [], jinhuo: [] }; this.syncQueue = Promise.resolve(); this.entiyeText = { bancai: '板材已存在', dingdan: '订单已存在', mupi: '木皮已存在', chanpin: '产品已存在', kucun: '已有库存记录', chanpin_zujian: '产品已有该组件', dingdan_bancai: '', zujian: '组件已定义过了', caizhi: '材质已定义过了', dingdan_chanpin: '订单下已有该产品', user: '' }; this.syncInterval = 5 * 60 * 1000; // 5分钟 this.storageKey = 'miniProgramData'; // 本地存储的键名 } get data() { // 创建数据代理 const handler = { get: (target, prop) => { // 处理特殊属性 if (prop.startsWith('_')) { return target[prop]; } // 处理数组类型的实体集合 if (Array.isArray(target[prop])) { return target[prop].map(item => this.lazyLoader.createProxy(item, prop.replace(/s$/, '')) ); } // 默认返回原始值 return target[prop]; }, // 保持其他操作不变 set: (target, prop, value) => { target[prop] = value; return true; } }; return new Proxy(this._rawData, handler); } // 添加显式初始化方法 async initialize() { // 启动自动同步 this.startAutoSync(); // 执行首次数据同步 await this.syncData(); } /** * 启动自动同步定时器 * 每隔syncInterval毫秒检查并执行数据同步 * 如果已有同步任务进行中则跳过 */ startAutoSync() { if (this.autoSyncTimer) clearInterval(this.autoSyncTimer); this.autoSyncTimer = setInterval(() => { if (!this.isSyncing) this.syncData(); }, this.syncInterval); } /** * 停止自动同步 */ stopAutoSync() { clearInterval(this.autoSyncTimer); } /** * 检查网络状态 */ checkNetwork() { return new Promise((resolve) => { wx.getNetworkType({ success: (res) => { resolve(res.networkType); }, fail: () => { resolve('unknown'); } }); }); } /** * 获取所有数据(全量或增量) * @async * @param {string} [since] - 增量获取的时间戳,不传则全量获取 * @returns {Promise<boolean>} 是否获取成功 * @description * - 根据since参数决定全量或增量获取数据 * - 增量获取时会合并新数据到现有数据 * - 全量获取会直接替换现有数据 * - 成功后会更新同步时间并保存到本地存储 * - 失败时会触发错误回调,若无历史数据则抛出错误 */ async fetchAll(since) { try { console.log(since ? `增量获取数据(自${since})...` : '全量获取数据...'); const params = since ? { since } : {}; const result = await this.request('/app/all', 'GET', params); const resolvedData =result ; // 更新networkData Object.keys(this._rawData).forEach(key => { if (key.startsWith('_')) return; if (resolvedData[key]) { if (since) { // 增量更新: 合并新数据到现有数据 resolvedData[key].forEach(newItem => { const index = this._rawData[key].findIndex(item => item.id === newItem.id); if (index >= 0) { this._rawData[key][index] = newItem; } else { this._rawData[key].push(newItem); } }); } else { // 全量更新: 直接替换 this._rawData[key] = resolvedData[key]; } } }); // 更新同步时间 this.lastSync = new Date(); this._rawData._lastSync = this.lastSync.toISOString(); // 保存到本地存储 this.saveDataToStorage(); this.triggerCallbacks('refresh', 'all', this.data); return true; } catch (error) { console.error('Fetch error:', error); this.triggerCallbacks('fetch_error', 'all', { error }); // 失败时尝试使用本地数据 if (!this.lastSync) { throw new Error('初始化数据获取失败'); } return false; } } /** * 微信小程序API请求封装 */ request(url, method = 'GET', data = null, retryCount = 3) { return new Promise((resolve, reject) => { const makeRequest = (attempt) => { const fullUrl = `${this.baseUrl}${url}`; if (this.debug) { console.log(`[请求] ${method} ${fullUrl}`, { attempt, data, timestamp: new Date().toISOString() }); } wx.request({ url: fullUrl, method, data, header: { 'Content-Type': 'application/json' }, success: (res) => { if (this.debug) { console.log(`[响应] ${fullUrl}`, { status: res.statusCode, data: res.data, headers: res.header }); } // 修复:更灵活的响应格式处理 if (!res.data) { const err = new Error('空响应数据'); if (attempt < retryCount) { this.retryRequest(makeRequest, attempt, retryCount, err); } else { reject(err); } return; } // 修复:支持多种成功状态码和响应格式 const isSuccess = res.statusCode >= 200 && res.statusCode < 300; const hasData = res.data && (res.data.data !== undefined || typeof res.data === 'object'); if (isSuccess && hasData) { resolve(res.data.data || res.data); } else { const errMsg = res.data.message || res.data.text || 'API错误'; const err = new Error(errMsg); if (attempt < retryCount) { this.retryRequest(makeRequest, attempt, retryCount, err); } else { reject(err); } } }, fail: (err) => { if (this.debug) { console.error(`[失败] ${fullUrl}`, err); } const error = new Error(`网络请求失败: ${err.errMsg || '未知错误'}`); if (attempt < retryCount) { this.retryRequest(makeRequest, attempt, retryCount, error); } else { reject(error); } } }); }; makeRequest(1); }); } retryRequest(makeRequest, attempt, retryCount, error) { const delay = 1000 * attempt; console.warn(`请求失败 (${attempt}/${retryCount}), ${delay}ms后重试:`, error.message); setTimeout(() => makeRequest(attempt + 1), delay); } /** * 注册回调函数 */ registerCallback(entity, callback) { if (!this.callbacks[entity]) { this.callbacks[entity] = []; } this.callbacks[entity].push(callback); } /** * 注销回调函数 */ unregisterCallback(entity, callback) { if (!this.callbacks[entity]) return; const index = this.callbacks[entity].indexOf(callback); if (index !== -1) { this.callbacks[entity].splice(index, 1); } } /** * 触发回调函数 */ triggerCallbacks(operation, entity, data) { this.callbacks.all.forEach(cb => cb(operation, entity, data)); if (this.callbacks[entity]) { this.callbacks[entity].forEach(cb => cb(operation, data)); } } /** * 检查重复实体 */ checkDuplicate(entity, data) { // 修复:确保引用已解析 const resolvedData = resolveDataReference(data, this.data); switch (entity) { case 'bancai': return this.data.bancais.some(b => b.houdu === resolvedData.houdu && b.caizhi?.id === resolvedData.caizhi?.id && b.mupi1?.id === resolvedData.mupi1?.id && b.mupi2?.id === resolvedData.mupi2?.id ); case 'caizhi': return this.data.caizhis.some(c => c.name === resolvedData.name); case 'mupi': return this.data.mupis.some(m => m.name === resolvedData.name && m.you === resolvedData.you); case 'chanpin': return this.data.chanpins.some(c => c.bianhao === resolvedData.bianhao); case 'zujian': return this.data.zujians.some(z => z.name === resolvedData.name); case 'dingdan': return this.data.dingdans.some(d => d.number === resolvedData.number); case 'chanpin_zujian': return this.data.chanpin_zujians.some(cz => cz.chanpin?.id === resolvedData.chanpin?.id && cz.zujian?.id === resolvedData.zujian?.id ); case 'dingdan_chanpin': return this.data.dingdan_chanpins.some(dc => dc.dingdan?.id === resolvedData.dingdan?.id && dc.chanpin?.id === resolvedData.chanpin?.id ); case 'dingdan_bancai': return this.data.dingdan_bancais.some(db => db.dingdan?.id === resolvedData.dingdan?.id && db.chanpin?.id === resolvedData.chanpin?.id && db.zujian?.id === resolvedData.zujian?.id && db.bancai?.id === resolvedData.bancai?.id ); case 'user': return this.data.users.some(u => u.name === resolvedData.name); default: return false; } } /** * CRUD操作通用方法 */ async crudOperation(operation, entity, data) { try { // 使用微信请求API替代fetch const result = await this.request(`/app/${operation}/${entity}`, 'POST', data); this.updateLocalData(operation, entity, result || data); this.triggerCallbacks(operation, entity, result || data); return result; } catch (error) { console.error('CRUD error:', error); this.triggerCallbacks(`${operation}_error`, entity, { data, error: error.message }); throw error; } } /** * 更新本地数据 */ /** * 更新本地数据 * @param {string} operation - 操作类型: 'add' | 'update' | 'delete' * @param {string} entity - 实体名称 * @param {Object} newData - 新数据对象(包含id字段) * @description 根据操作类型对本地数据进行增删改操作 */ updateLocalData(operation, entity, newData) { const key = `${entity}s`; const entities = this._rawData[key]; // 确保新数据的引用已解析 const resolvedData = resolveDataReference(newData, this._rawData); switch (operation) { case 'add': entities.push(resolvedData); break; case 'update': const index = entities.findIndex(item => item.id === resolvedData.id); if (index !== -1) { // 修复:使用对象展开操作符确保属性完整覆盖 entities[index] = { ...entities[index], ...resolvedData }; } else { entities.push(resolvedData); } break; case 'delete': const deleteIndex = entities.findIndex(item => item.id === resolvedData.id); if (deleteIndex !== -1) { entities.splice(deleteIndex, 1); } break; } // 更新最后修改时间 this._rawData._lastModified = new Date().toISOString(); // 保存修改后的数据到本地存储 this.saveDataToStorage(); } /** * 同步数据 */ /** * 同步数据方法 * 该方法用于异步获取所有数据,并处理同步过程中的并发请求 * 如果同步正在进行中,会将请求标记为待处理(pendingSync) * 同步完成后会自动处理待处理的请求 * @async * @throws {Error} 当获取数据失败时会抛出错误并记录日志 */ async syncData() { if (this.isSyncing) { this.pendingSync = true; return; } this.isSyncing = true; try { // 1. 先加载本地数据 this.loadDataFromStorage(); // 2. 获取最后同步时间,用于增量更新 const since = this._rawData._lastSync || null; // 3. 获取增量数据 await this.fetchAll(since); // 4. 保存更新后的数据到本地存储 this.saveDataToStorage(); // 5. 触发数据更新回调 this.triggerCallbacks('refresh', 'all', this.data); } catch (error) { console.error('Sync failed:', error); this.triggerCallbacks('sync_error', 'all', { error }); // 失败时尝试使用本地数据 if (!this._rawData._lastSync) { throw new Error('初始化数据同步失败'); } } finally { this.isSyncing = false; if (this.pendingSync) { this.pendingSync = false; this.syncData(); } } } /** * 从本地存储加载数据 * 使用微信小程序的同步存储API获取之前保存的数据 */ loadDataFromStorage() { try { const storedData = wx.getStorageSync(this.storageKey); if (storedData) { // 修复:加载到_rawData而非data代理对象 this._rawData = storedData; } } catch (error) { console.error('加载本地存储数据失败:', error); // 提供默认空数据 this._rawData = { bancais: [], dingdans: [], mupis: [], chanpins: [], kucuns: [], dingdan_bancais: [], chanpin_zujians: [], zujians: [], caizhis: [], dingdan_chanpins: [], users: [], jinhuos: [], _lastModified: null, _lastSync: null }; } } /** * 保存数据到本地存储 * 使用微信小程序的同步存储API持久化当前数据 */ saveDataToStorage() { try { // 修复:保存_rawData而非localData wx.setStorageSync(this.storageKey, this._rawData); } catch (error) { console.error('保存数据到本地存储失败:', error); // 提示用户或执行降级策略 wx.showToast({ title: '数据保存失败,请稍后重试', icon: 'none' }); } } /** * 添加实体 */ /** * 添加实体数据 * @async * @param {string} entity - 实体类型 * @param {Object} data - 要添加的实体数据 * @returns {Promise} 返回CRUD操作结果 * @throws {Error} 如果数据已存在则抛出错误 */ async addEntity(entity, data) { if (this.checkDuplicate(entity, data)) { const errorMsg = `${this.entiyeText[entity]}`; this.triggerCallbacks('duplicate_error', entity, { data, error: errorMsg }); throw new Error(errorMsg); } return this.crudOperation('add', entity, data); } /** * 更新实体 */ async updateEntity(entity, data) { return this.crudOperation('update', entity, data); } /** * 删除实体 */ async deleteEntity(entity, id) { return this.crudOperation('delete', entity, { id }); } getBancaisForZujian(zujianId) { const dingdan_bancais = this.data.dingdan_bancais.filter(db => db.zujian?.id == zujianId); return dingdan_bancais.map(db => db.bancai).filter(Boolean); } /** * 获取板材的库存信息 */ getKucunForBancai(bancaiId) { return this.data.kucuns.find(k => k.bancai?.id == bancaiId); } } // 导出模块 module.exports = MiniProgramDataManager;

romygreat
  • 粉丝: 20
上传资源 快速赚钱