// 引入lodash库，用于处理各种数据结构，提供了如对象操作、数组处理、排序等很多实用的工具函数，在后续代码中会频繁使用到。
var _ = require('lodash');
// 引入Node.js的path模块，主要用于处理文件路径相关操作，通过 `path.join` 方法拼接当前工作目录（`process.cwd()`）和相对路径，来准确引入自定义的 `dao/DAO` 等模块所在的路径。
var path = require("path");
// 引入自定义的 `DAO` 模块，该模块应该封装了通用的数据访问操作方法，例如查询、获取单条记录、创建、更新等操作，对应不同的数据模型（如 `GoodModel`、`GoodPicModel` 等）与数据库进行交互，执行相关的数据操作。
var dao = require(path.join(process.cwd(), "dao/DAO"));
// 引入自定义的 `GoodAttributeDAO` 模块，推测这个模块主要用于商品属性相关数据的访问操作，比如获取商品属性列表、清理商品属性等功能，同样是与数据库进行交互来操作商品属性数据。
var goodAttributeDao = require(path.join(process.cwd(), "dao/GoodAttributeDAO"));
// 引入 `orm` 库，可能用于构建数据库查询条件等操作，例如使用其提供的 `like` 方法来构建模糊查询条件，方便在数据库查询中进行模糊匹配筛选数据。
var orm = require("orm");
// 引入 `bluebird` 库，用于处理 `Promise` 相关的异步操作，使得异步代码的编写更加简洁和易于管理，通过返回 `Promise` 对象来实现异步流程的链式调用和错误处理等功能。
var Promise = require("bluebird");
// 引入Node.js的文件系统模块 `fs`，用于进行文件读取、写入、删除等操作，在处理商品图片的文件存储以及删除等相关功能时会用到。
var fs = require("fs");

// 引入 `gm` 库，用于对图片进行处理操作（虽然代码中当前部分相关逻辑被注释掉了，但推测可能是用于图片裁剪、缩放、格式转换等功能，比如原本计划通过 `gm` 来调整图片尺寸等操作）。
var gm = require("gm");
// 引入 `uniqid` 库，用于生成唯一的标识符，可能在处理商品相关数据时，为某些需要唯一标识的字段（比如图片标识等情况）生成唯一的ID值，但从当前代码看暂未明确体现其使用场景。
var uniqid = require('uniqid');
// 引入配置文件读取模块，并获取名为 `upload_config` 的配置信息，这个配置信息大概率与文件上传相关设置有关，比如文件上传的路径、基础URL等配置，用于后续处理商品图片的存储路径等操作时使用。
var upload_config = require('config').get("upload_config");

/**
 * 裁剪图片
 * 此函数用于对图片进行裁剪操作（虽然当前实现的逻辑是简单的文件流复制，原计划通过 `gm` 库实现更复杂的裁剪等功能），接收原始图片路径、存储路径、新的宽度和新的高度作为参数，
 * 返回一个 `Promise` 对象，用于异步处理裁剪操作完成后的结果，若操作成功则 `Promise` 被成功 `resolve`，若出现错误则 `Promise` 被 `reject`。
 * 
 * @param  {[type]} srcPath   原始图片路径，指定要进行裁剪操作的原始图片在文件系统中的位置，是读取图片数据的来源路径。
 * @param  {[type]} savePath  存储路径，指定裁剪后的图片要保存到文件系统中的位置，是写入裁剪后图片数据的目标路径。
 * @param  {[type]} newWidth  新的宽度，用于指定裁剪后图片期望的宽度尺寸，单位可能根据具体使用场景和图片处理库的要求而定（通常是像素单位）。
 * @param  {[type]} newHeight 新的高度，用于指定裁剪后图片期望的高度尺寸，单位通常也是像素单位，与 `newWidth` 一起确定裁剪后图片的尺寸大小。
 * @return {[type]}           返回一个 `Promise` 对象，用于异步处理图片裁剪操作的结果情况，外部可以通过 `.then` 和 `.catch` 方法来处理操作成功或失败后的逻辑。
 */
function clipImage(srcPath, savePath, newWidth, newHeight) {
    return new Promise(function (resolve, reject) {
        // 以下这两行代码原本可能是用于调试输出原始图片路径和存储路径信息的，方便在开发过程中查看图片处理时涉及的路径是否正确，目前被注释掉了，但保留注释可以作为调试时的参考。
        // console.log("src => %s",srcPath);
        // console.log("save => %s",savePath);

        /*
        gm(srcPath)
       .resize(newWidth,newHeight)
       .autoOrient()
       .write(savePath,function(err){
            resolve();
        })
        
        */

        // 创建读取流，通过 `fs` 模块的 `createReadStream` 方法从指定的原始图片路径（`srcPath`）创建一个可读流，用于读取图片文件的数据，以便后续进行处理或复制等操作。
        readable = fs.createReadStream(srcPath);
        // 创建写入流，使用 `fs` 模块的 `createWriteStream` 方法根据指定的存储路径（`savePath`）创建一个可写流，用于将处理后的图片数据（当前只是简单复制）写入到目标文件中，实现图片的“裁剪”（实际是复制）操作。
        writable = fs.createWriteStream(savePath);
        // 将可读流通过管道连接到可写流，这样可读流读取到的图片文件数据会自动写入到可写流对应的文件中，实现文件数据的复制功能，模拟简单的图片裁剪（实际未真正裁剪图片，只是复制文件）操作。
        readable.pipe(writable);
        // 为可读流添加 `'end'` 事件监听器，当可读流读取完所有数据（即图片文件数据读取完成）时，会触发该事件，此时通过调用 `resolve` 函数来表示图片“裁剪”（复制）操作成功完成，
        // 外部通过 `Promise` 的 `.then` 方法可以继续后续成功后的逻辑处理。
        readable.on('end', function () {
            resolve();
        });

    });
}

/**
 * 通过参数生成产品基本信息
 * 此函数用于根据传入的参数生成商品的基本信息对象，对各个参数进行合法性检查和格式转换等操作，返回一个 `Promise` 对象，
 * 若参数合法且信息生成成功则 `Promise` 被成功 `resolve` 并返回生成的商品基本信息对象，若参数不合法则 `Promise` 被 `reject` 并返回相应的错误信息。
 * 
 * @param  {[type]} params 参数对象，包含了创建或更新商品时需要的各种信息，例如商品名称（`goods_name`）、价格（`goods_price`）、数量（`goods_number`）、所属分类（`goods_cat`）等字段信息，具体要求由业务逻辑决定。
 * @return {[type]}           返回一个 `Promise` 对象，用于异步处理商品基本信息生成的结果情况，外部可以通过 `.then` 和 `.catch` 方法来处理操作成功或失败后的逻辑，成功时返回包含商品基本信息的对象，失败时返回错误信息字符串。
 */
function generateGoodInfo(params) {
    return new Promise(function (resolve, reject) {
        var info = {};
        // 如果传入的参数中包含 `goods_id`，则将其添加到 `info` 对象中，`goods_id` 通常用于标识商品的唯一ID，在更新商品等操作时可能会用到已有的商品ID来确定具体操作的商品对象。
        if (params.goods_id) info["goods_id"] = params.goods_id;

        // 检查商品名称是否存在，如果不存在则通过 `reject` 函数返回错误信息，表示商品名称不能为空，因为商品名称是商品的重要标识信息，必不可少，告知调用者参数不符合要求。
        if (!params.goods_name) return reject("商品名称不能为空");
        info["goods_name"] = params.goods_name;

        // 检查商品价格是否存在，如果不存在则通过 `reject` 函数返回错误信息，表示商品价格不能为空，因为价格是商品的关键属性之一，必须提供，告知调用者参数不符合要求。
        if (!params.goods_price) return reject("商品价格不能为空");
        var price = parseFloat(params.goods_price);
        // 进一步检查商品价格是否能正确转换为数字类型（通过 `isNaN` 判断）以及价格是否小于 `0`，如果不满足要求则通过 `reject` 函数返回错误信息，表示商品价格不正确，告知调用者参数不符合要求。
        if (isNaN(price) || price < 0) return reject("商品价格不正确")
        info["goods_price"] = price;

        // 检查商品数量是否存在，如果不存在则通过 `reject` 函数返回错误信息，表示商品数量不能为空，因为商品数量是商品的基本属性之一，必须明确，告知调用者参数不符合要求。
        if (!params.goods_number) return reject("商品数量不能为空");
        var num = parseInt(params.goods_number);
        // 检查商品数量是否能正确转换为数字类型以及数量是否小于 `0`，若不符合要求则通过 `reject` 函数返回错误信息，表示商品数量不正确，告知调用者参数不符合要求。
        if (isNaN(num) || num < 0) return reject("商品数量不正确");
        info["goods_number"] = num;

        // 检查商品所属分类是否设置，如果没有设置则通过 `reject` 函数返回错误信息，表示商品没有设置所属分类，因为分类信息对于商品的归类和管理很重要，必须提供，告知调用者参数不符合要求。
        if (!params.goods_cat) return reject("商品没有设置所属分类");
        var cats = params.goods_cat.split(',');
        // 如果分类信息分割后数组长度大于 `0`，说明有一级分类信息，将其赋值给 `info` 对象的 `cat_one_id` 属性，用于存储商品的一级分类标识，方便后续业务逻辑根据分类进行查询、展示等操作。
        if (cats.length > 0) {
            info["cat_one_id"] = cats[0];
        }
        // 如果分类信息数组长度大于 `1`，说明有二级分类信息，将其赋值给 `info` 对象的 `cat_two_id` 属性，用于存储商品的二级分类标识，进一步细化商品的分类情况。
        if (cats.length > 1) {
            info["cat_two_id"] = cats[1];
        }
        // 如果分类信息数组长度大于 `2`，说明有三级分类信息，将其赋值给 `info` 对象的 `cat_three_id` 属性，并同时将三级分类标识赋值给 `cat_id` 属性（可能在某些业务场景下以 `cat_id` 作为统一的分类标识使用），完善商品的分类相关信息记录。
        if (cats.length > 2) {
            info["cat_three_id"] = cats[2];
            info["cat_id"] = cats[2];
        }

        // 如果传入的参数中包含 `goods_weight`（商品重量），则进行以下操作，先将其转换为数字类型（通过 `parseFloat`），然后检查是否能正确转换以及重量是否小于 `0`，
        // 如果不满足要求则通过 `reject` 函数返回错误信息，表示商品重量格式不正确，告知调用者参数不符合要求；若格式正确则将重量值赋值给 `info` 对象的 `goods_weight` 属性，用于记录商品的重量信息。
        if (params.goods_weight) {
            weight = parseFloat(params.goods_weight);
            if (isNaN(weight) || weight < 0) return reject("商品重量格式不正确");
            info["goods_weight"] = weight;
        } else {
            // 如果没有传入商品重量参数，则默认将商品重量设置为 `0`，作为合理的默认值，方便后续业务逻辑处理重量相关情况，避免出现 `undefined` 等不确定的值导致错误。
            info["goods_weight"] = 0;
        }

        // 如果传入的参数中包含 `goods_introduce`（商品介绍信息），则将其直接赋值给 `info` 对象的 `goods_introduce` 属性，用于存储商品的详细介绍内容，方便在展示商品详情等场景中使用。
        if (params.goods_introduce) {
            info["goods_introduce"] = params.goods_introduce;
        }

        // 如果传入的参数中包含 `goods_big_logo`（商品大图标路径或相关标识等信息），则将其赋值给 `info` 对象的 `goods_big_logo` 属性，用于记录商品大图标相关信息，方便后续业务逻辑处理图标展示等操作。
        if (params.goods_big_logo) {
            info["goods_big_logo"] = params.goods_big_logo;
        } else {
            // 如果没有传入商品大图标信息，则默认将 `goods_big_logo` 属性设置为空字符串，避免出现 `undefined` 等不确定的值，方便后续业务逻辑处理图标相关情况。
            info["goods_big_logo"] = "";
        }

        // 如果传入的参数中包含 `goods_small_logo`（商品小图标路径或相关标识等信息），则将其赋值给 `info` 对象的 `goods_small_logo` 属性，用于记录商品小图标相关信息，方便后续业务逻辑处理图标展示等操作。
        if (params.goods_small_logo) {
            info["goods_small_logo"] = params.goods_small_logo;
        } else {
            // 如果没有传入商品小图标信息，则默认将 `goods_small_logo` 属性设置为空字符串，避免出现 `undefined` 等不确定的值，方便后续业务逻辑处理图标相关情况。
            info["goods_small_logo"] = "";
        }

        // 如果传入的参数中包含 `goods_state`（商品状态信息，比如是否上架、是否禁用等状态标识，具体含义由业务逻辑定义），则将其赋值给 `info` 对象的 `goods_state` 属性，用于记录商品的状态信息，方便后续业务逻辑根据状态进行不同的处理操作。
        if (params.goods_state) {
            info["goods_state"] = params.goods_state;
        }

        // 如果传入的参数中包含 `pics`（商品图片相关信息，可能是图片路径数组或者包含图片相关属性的对象数组等形式，具体格式由业务逻辑决定），则将其赋值给 `info` 对象的 `pics` 属性，用于记录商品的图片信息，方便后续业务逻辑处理图片展示、更新等操作。
        if (params.pics) {
            info["pics"] = params.pics;
        }

        // 如果传入的参数中包含 `attrs`（商品属性相关信息，可能是属性名和属性值的对象数组等形式，具体格式由业务逻辑决定），则将其赋值给 `info` 对象的 `attrs` 属性，用于记录商品的属性信息，方便后续业务逻辑处理属性展示、更新等操作。
        if (params.attrs) {
            info["attrs"] = params.attrs;
        }

        // 获取当前时间并转换为时间戳（以秒为单位，通过除以 `1000` 将毫秒时间戳转换为秒时间戳），将其赋值给 `info` 对象的 `add_time` 属性，用于记录商品的添加时间，方便后续业务逻辑根据添加时间进行排序、查询等操作。
        info["add_time"] = Date.parse(new Date()) / 1000;
        // 同样获取当前时间并转换为时间戳赋值给 `info` 对象的 `upd_time` 属性，用于记录商品的最后更新时间，方便后续业务逻辑判断商品信息是否有更新以及进行更新时间相关的查询等操作。
        info["upd_time"] = Date.parse(new Date()) / 1000;
        // 将商品的删除标识默认设置为 `0`（通常 `0` 表示未删除，`1` 等其他值可能表示已删除，具体由业务逻辑定义），赋值给 `info` 对象的 `is_del` 属性，用于标记商品的删除状态，方便后续业务逻辑进行删除相关的操作和判断。
        info["is_del"] = '0';

        // 如果传入的参数中包含 `hot_mumber`（热销品数量相关信息，比如表示商品的销量或者热门程度的数量指标，具体含义由业务逻辑定义），则进行以下操作，
        // 先将其转换为数字类型（通过 `parseInt`），然后检查是否能正确转换以及数量是否小于 `0`，如果不满足要求则通过 `reject` 函数返回错误信息，表示热销品数量格式不正确，告知调用者参数不符合要求；
        // 若格式正确则将数量值赋值给 `info` 对象的 `hot_mumber` 属性，用于记录商品的热销相关信息，方便后续业务逻辑根据热销情况进行推荐、排序等操作。
        if (params.hot_mumber) {
            hot_num = parseInt(params.hot_mumber);
            if (isNaN(hot_num) || hot_num < 0) return reject("热销品数量格式不正确");
            info["hot_mumber"] = hot_num;
        } else {
            // 如果没有传入热销品数量参数，则默认将其设置为 `0`，作为合理的默认值，方便后续业务逻辑处理热销相关情况，避免出现 `undefined` 等不确定的值导致错误。
// 如果 `info` 对象中已经存在 `is_promote` 属性（可能表示商品是否参与促销等促销相关标识，具体含义由业务逻辑定义），则直接使用其原有值；
// 如果不存在，则默认将其设置为 `false`，表示商品默认不参与促销，方便后续业务逻辑根据该属性进行促销相关的判断和处理操作。
info["is_promote"] = info["is_promote"]? info["is_promote"] : false;

// 将 `params` 对象中的 `goods_introduce` 属性值重新赋值给 `info` 对象的 `goods_introduce` 属性，确保商品介绍信息准确更新（可能前面的逻辑中有对该属性的其他处理或者验证等情况），方便后续使用完整准确的商品介绍信息。
info["goods_introduce"] = params.goods_introduce;

// 通过 `resolve` 函数将包含整理好的商品基本信息的 `info` 对象返回，表明商品基本信息生成成功，外部通过 `Promise` 的 `.then` 方法可以获取到这个对象并继续后续的业务逻辑操作，比如进一步检查商品名称是否重复等操作。
resolve(info);
});
}

/**
 * 检查商品名称是否重复
 * 此函数用于检查要创建或更新的商品名称在数据库中是否已经存在（仅考虑未删除的商品记录），返回一个 `Promise` 对象，
 * 若名称不存在或者是当前操作的商品自身（通过 `goods_id` 判断）则 `Promise` 被成功 `resolve` 并返回商品信息对象，若名称已存在则 `Promise` 被 `reject` 并返回相应错误信息。
 * 
 * @param  {[type]} info 包含商品信息的对象，其中 `goods_name` 属性表示商品名称，`goods_id` 属性（如果存在）可用于区分是创建新商品还是更新已有商品时的名称检查情况，方便判断是否为同一个商品。
 * @return {[type]}      返回一个 `Promise` 对象，用于异步处理商品名称重复检查的结果情况，外部可以通过 `.then` 和 `.catch` 方法来处理操作成功或失败后的逻辑，成功时返回商品信息对象，失败时返回错误信息字符串。
 */
function checkGoodName(info) {
    return new Promise(function (resolve, reject) {

        // 调用 `dao` 模块的 `findOne` 方法，在 `GoodModel` 数据模型（对应数据库中的商品表相关操作）中查找符合条件的商品记录，
        // 条件是商品名称为 `info.goods_name` 且 `is_del`（商品删除标识，通常 `0` 表示未删除，`1` 表示已删除）为 `0`，即查找未删除且名称相同的商品记录，
        // 这是一个异步操作，通过传入的回调函数来处理查询结果，若查询出现错误则 `err` 不为 `null`，若查询到符合条件的商品记录则返回该记录对象 `good`，若未查询到则 `good` 为 `null`。
        dao.findOne("GoodModel", {"goods_name": info.goods_name, "is_del": "0"}, function (err, good) {
            // 如果在查询过程中出现错误（`err` 不为 `null`），则通过 `reject` 函数返回错误信息，告知调用者名称检查操作失败及原因，外部通过 `Promise` 的 `.catch` 方法可以捕获并处理该错误情况。
            if (err) return reject(err);

            // 如果没有查询到符合条件的商品记录（即 `good` 为 `null`），说明商品名称不存在重复情况，此时通过 `resolve` 函数返回传入的 `info` 对象，表明名称检查通过，可以继续后续的业务操作（比如创建或更新商品等操作）。
            if (!good) return resolve(info);

            // 如果查询到了符合条件的商品记录，进一步判断其 `goods_id` 是否与传入的 `info` 对象中的 `goods_id` 相等，若相等说明是当前正在操作的同一个商品（比如更新商品时名称未变的情况），
            // 此时也通过 `resolve` 函数返回 `info` 对象，表示名称检查通过，可以继续后续操作；若 `goods_id` 不相等，则说明存在其他同名的未删除商品，即商品名称已重复，通过 `reject` 函数返回相应错误信息，告知调用者名称重复不能继续操作。
            if (good.goods_id == info.goods_id) return resolve(info);
            return reject("商品名称已存在");
        });
    });
}

/**
 * 创建商品基本信息
 * 此函数用于将整理好的商品基本信息插入数据库（对应 `GoodModel` 数据模型）中，返回一个 `Promise` 对象，
 * 若插入操作成功则 `Promise` 被成功 `resolve` 并返回包含商品信息和插入后商品对象的 `info` 对象，若插入失败则 `Promise` 被 `reject` 并返回相应错误信息。
 * 
 * @param  {[type]} info 包含商品基本信息的对象，经过前面的生成和名称检查等操作后得到的要插入数据库的商品信息，包含商品名称、价格、数量等各种属性信息。
 * @return {[type]}      返回一个 `Promise` 对象，用于异步处理创建商品基本信息的结果情况，外部可以通过 `.then` 和 `.catch` 方法来处理操作成功或失败后的逻辑，成功时返回包含商品信息和插入后商品对象的 `info` 对象，失败时返回错误信息字符串。
 */
function createGoodInfo(info) {

    return new Promise(function (resolve, reject) {
        // 调用 `dao` 模块的 `create` 方法，将 `info` 对象中的商品基本信息插入到 `GoodModel` 对应的数据表中，
        // 为了避免对原始 `info` 对象产生意外修改，先使用 `_.clone` 对其进行克隆（通过 `lodash` 库的克隆功能）后再传入 `create` 方法，
        // 这是一个异步操作，通过传入的回调函数来处理插入操作的结果，若插入出现错误则 `err` 不为 `null`，若插入成功则返回新创建的商品对象 `newGood`，包含数据库自动生成的一些字段（如 `id` 等）以及插入的商品信息。
        dao.create("GoodModel", _.clone(info), function (err, newGood) {

            // 如果在创建商品基本信息的数据库插入操作过程中出现错误（`err` 不为 `null`），则通过 `reject` 函数返回错误信息，表示创建商品基本信息失败，告知调用者操作未成功及原因，外部通过 `Promise` 的 `.catch` 方法可以捕获并处理该错误情况。
            if (err) return reject("创建商品基本信息失败");

            // 如果插入成功，调用新创建的商品对象 `newGood` 的 `getGoodsCat` 方法（具体功能由该方法的定义决定，可能是获取商品分类相关信息并进行处理等操作），
            // 将返回的分类信息赋值给 `newGood` 对象的 `goods_cat` 属性，确保商品的分类信息准确完整，然后将 `newGood` 对象赋值给 `info` 对象的 `good` 属性，方便后续业务逻辑使用完整的商品对象信息。
            newGood.goods_cat = newGood.getGoodsCat();
            info.good = newGood;

            // 通过 `resolve` 函数返回包含商品信息和插入后商品对象的 `info` 对象，表明创建商品基本信息操作成功，外部通过 `Promise` 的 `.then` 方法可以获取到这个对象并继续后续的业务逻辑操作，比如更新商品图片等操作。
            return resolve(info);
        });
    });
}

function updateGoodInfo(info) {
    return new Promise(function (resolve, reject) {
        // 检查 `info` 对象中是否包含 `goods_id` 属性，如果不存在则通过 `reject` 函数返回错误信息，表示商品ID不存在，因为更新操作需要明确要更新的商品对象的唯一标识（即 `goods_id`），告知调用者参数不符合要求，外部通过 `Promise` 的 `.catch` 方法可以捕获并处理该错误情况。
        if (!info.goods_id) return reject("商品ID不存在");

        // 调用 `dao` 模块的 `update` 方法，根据 `info` 对象中的 `goods_id` 来更新 `GoodModel` 对应数据表中的商品记录信息，
        // 同样先使用 `_.clone` 克隆 `info` 对象后传入 `update` 方法，避免对原始 `info` 对象意外修改，
        // 这是一个异步操作，通过传入的回调函数来处理更新操作的结果，若更新出现错误则 `err` 不为 `null`，若更新成功则返回更新后的商品对象 `newGood`。
        dao.update("GoodModel", info.goods_id, _.clone(info), function (err, newGood) {
            // 如果在更新商品基本信息的数据库操作过程中出现错误（`err` 不为 `null`），则通过 `reject` 函数返回错误信息，表示更新商品基本信息失败，告知调用者操作未成功及原因，外部通过 `Promise` 的 `.catch` 方法可以捕获并处理该错误情况。
            if (err) return reject("更新商品基本信息失败");

            // 如果更新成功，将更新后的商品对象 `newGood` 赋值给 `info` 对象的 `good` 属性，方便后续业务逻辑使用最新的商品对象信息。
            info.good = newGood;

            // 通过 `resolve` 函数返回包含商品信息和更新后商品对象的 `info` 对象，表明更新商品基本信息操作成功，外部通过 `Promise` 的 `.then` 方法可以获取到这个对象并继续后续的业务逻辑操作，比如更新商品图片等操作。
            return resolve(info);
        });

    });
}

/**
 * 获取商品对象
 * 此函数用于根据传入的商品ID从数据库中获取对应的商品对象信息（对应 `GoodModel` 数据模型），返回一个 `Promise` 对象，
 * 若商品ID格式正确且查询成功则 `Promise` 被成功 `resolve` 并返回包含商品信息和查询到的商品对象的 `info` 对象，若商品ID格式不正确或查询失败则 `Promise` 被 `reject` 并返回相应错误信息。
 * 
 * @param  {[type]} info 包含查询条件的对象，其中 `goods_id` 属性用于指定要获取的商品的唯一标识，若该属性不存在、值为 `null` 或者不能转换为数字类型，则视为商品ID格式不正确，无法进行查询操作。
 * @return {[type]}      返回一个 `Promise` 对象，用于异步处理获取商品对象的结果情况，外部可以通过 `.then` 和 `.catch` 方法来处理操作成功或失败后的逻辑，成功时返回包含商品信息和查询到的商品对象的 `info` 对象，失败时返回错误信息字符串。
 */
function getGoodInfo(info) {
    return new Promise(function (resolve, reject) {
        // 检查 `info` 对象是否存在、是否包含 `goods_id` 属性以及 `goods_id` 属性的值是否能正确转换为数字类型（通过 `isNaN` 判断），
        // 如果不满足这些条件，则通过 `reject` 函数返回错误信息，表示商品ID格式不正确，告知调用者参数不符合要求，无法进行查询操作，外部通过 `Promise` 的 `.catch` 方法可以捕获并处理该错误情况。
        if (!info ||!info.goods_id || isNaN(info.goods_id)) return reject("商品ID格式不正确");

        // 调用 `dao` 模块的 `show` 方法，根据 `info` 对象中的 `goods_id` 从 `GoodModel` 对应的数据表中获取对应的商品对象信息，
        // 这是一个异步操作，通过传入的回调函数来处理查询结果，若查询出现错误则 `err` 不为 `null`，若查询成功则返回对应的商品对象 `good`。
        dao.show("GoodModel", info.goods_id, function (err, good) {
            // 如果在获取商品对象信息的数据库查询操作过程中出现错误（`err` 不为 `null`），则通过 `reject` 函数返回错误信息，表示获取商品基本信息失败，告知调用者操作未成功及原因，外部通过 `Promise` 的 `.catch` 方法可以捕获并处理该错误情况。
            if (err) return reject("获取商品基本信息失败");

            // 如果查询成功，调用查询到的商品对象 `good` 的 `getGoodsCat` 方法来处理商品分类相关信息（具体功能由该方法定义决定），
            // 然后将查询到的商品对象 `good` 赋值给 `info` 对象的 `good` 属性，方便后续业务逻辑使用完整的商品对象信息。
            good.goods_cat = good.getGoodsCat();
            info["good"] = good;

            // 通过 `resolve` 函数返回包含商品信息和查询到的商品对象的 `info` 对象，表明获取商品对象信息操作成功，外部通过 `Promise` 的 `.then` 方法可以获取到这个对象并继续后续的业务逻辑操作，比如更新商品图片等操作。
            return resolve(info);
        });
    });
}

/**
 * 删除商品图片
 * 此函数用于删除指定的商品图片记录（具体的删除操作由 `pic` 对象的 `remove` 方法实现，该方法应该是在对应的数据模型或对象中定义的与数据库交互的删除操作），返回一个 `Promise` 对象，
 * 若 `pic` 对象存在且有 `remove` 方法并且删除操作成功则 `Promise` 被成功 `resolve`，若 `pic` 对象不符合要求或者删除失败则 `Promise` 被 `reject` 并返回相应错误信息。
 * 
 * @param  {[type]} pic 图片对象，应该是对应商品图片的数据模型对象，包含了图片相关的各种属性以及操作方法（如 `remove` 方法用于删除图片记录），具体结构和功能由业务逻辑中对图片对象的定义决定。
 * @return {[type]}     返回一个 `Promise` 对象，用于异步处理删除商品图片记录的结果情况，外部可以通过 `.then` 和 `.catch` 方法来处理操作成功或失败后的逻辑，成功时无返回值（`Promise` 被成功 `resolve`），失败时返回错误信息字符串。
 */
function removeGoodPic(pic) {
    return new Promise(function (resolve, reject) {
        // 检查 `pic` 对象是否存在以及是否包含 `remove` 方法，如果不满足条件则通过 `reject` 函数返回错误信息，表示删除商品图片记录失败，告知调用者参数不符合要求，无法进行删除操作，外部通过 `Promise` 的 `.catch` 方法可以捕获并处理该错误情况。
        if (!pic ||!pic.remove) return reject("删除商品图片记录失败");

        // 调用 `pic` 对象的 `remove` 方法来执行删除图片记录的操作，这是一个异步操作，通过传入的回调函数来处理删除操作的结果，若删除出现错误则 `err` 不为 `null`，若删除成功则无额外返回数据（通常意味着删除操作顺利完成）。
        pic.remove(function (err) {
            // 如果在删除操作过程中出现错误（`err` 不为 `null`），则通过 `reject` 函数返回错误信息，表示删除失败，告知调用者操作未成功及原因，外部通过 `Promise` 的 `.catch` 方法可以捕获并处理该错误情况。
            if (err) return reject("删除失败");

            // 如果删除操作成功，通过 `resolve` 函数表示删除商品图片记录操作成功完成，外部通过 `Promise` 的 `.then` 方法可以继续后续成功后的逻辑处理（比如继续处理其他相关图片的删除或更新等操作）。
            resolve();
        });
    });
}

function removeGoodPicFile(path) {
    return new Promise(function (resolve, reject) {
        // 调用 `fs` 模块的 `unlink` 方法，根据传入的文件路径（`path`）来删除对应的文件（这里用于删除商品图片的物理文件），
        // 这是一个异步操作，通过传入的回调函数来处理删除文件操作的结果，虽然 `unlink` 方法的回调函数有两个参数（`err` 和 `result`），但从当前代码看只关注错误情况，若删除出现错误则 `err` 不为 `null`，若删除成功则无额外返回数据（意味着文件删除顺利完成）。
        fs.unlink(path, function (err, result) {
            // 通过 `resolve` 函数表示删除商品图片文件操作成功完成，外部通过 `Promise` 的 `.then` 方法可以继续后续成功后的逻辑处理（比如继续处理其他相关图片的删除或更新等操作），
            // 这里不处理 `result` 参数，因为当前代码逻辑只关心文件是否成功删除，不需要对删除操作的其他返回结果进行处理。
            resolve();
        });
    });
}

function createGoodPic(pic) {
    return new Promise(function (resolve, reject) {
        // 检查 `pic` 对象是否存在，如果不存在则通过 `reject` 函数返回错误信息，表示图片对象不能为空，因为创建图片数据需要有效的图片对象信息，告知调用者参数不符合要求，外部通过 `Promise` 的 `.catch` 方法可以捕获并处理该错误情况。
        if (!pic) return reject("图片对象不能为空");

        // 调用 `dao` 模块的 `getModel` 方法获取 `GoodPicModel` 数据模型对象（对应数据库中商品图片表相关操作的模型），然后调用该模型对象的 `create` 方法，
        // 将 `pic` 对象中的图片信息插入到数据库中对应的商品图片表中，创建新的图片数据记录，这是一个异步操作，通过传入的回调函数来处理插入操作的结果，若插入出现错误则 `err` 不为 `null`，若插入

/**
 * 更新商品图片
 * 该函数用于处理商品图片的更新操作，涉及对已有商品图片的删除、新增图片的处理以及相应数据库和文件系统操作等一系列流程，
 * 通过返回Promise对象来异步处理整个更新过程，并根据操作结果成功或失败来决定是resolve还是reject这个Promise。
 *
 * @param  {[type]} info    参数，包含了与商品相关的各种信息以及和图片更新相关的新图片数据等内容，用于在更新图片时进行各种条件判断和相应操作。
 * @param  {[type]} newGood 商品基本信息（在当前代码逻辑中，可能原本有更紧密的关联使用方式，但目前从代码上看部分作用不太明显，不过理论上也是包含商品关键属性等用于辅助操作的对象）。
 */
function doUpdateGoodPics(info) {
    // 创建并返回一个Promise对象，用于异步处理更新商品图片的整个操作流程，外部代码可以通过.then()方法处理操作成功的情况，通过.catch()方法处理操作失败的情况。
    return new Promise(function (resolve, reject) {
        var good = info.good;
        // 首先检查商品对象（从传入的info中获取）是否有goods_id属性，如果不存在该属性，意味着无法明确要更新图片所属的具体商品，
        // 这种情况下直接调用reject()函数拒绝这个Promise，并返回相应的错误信息，表示更新商品图片失败，告知外部调用者无法进行图片更新操作。
        if (!good.goods_id) return reject("更新商品图片失败");

        // 接着判断传入的info对象中是否包含pics属性，如果不存在该属性，说明没有新的图片相关信息需要处理，
        // 此时直接调用resolve()函数将info对象返回，表示图片更新环节无需进行后续操作，可直接通过，外部代码可以继续执行后续的业务逻辑。
        if (!info.pics) return resolve(info);

        // 使用dao模块的list方法从GoodPicModel对应的数据库表中查询指定商品（通过good.goods_id来确定具体是哪个商品）的所有图片记录，
        // 这是一个异步操作，会通过传入的回调函数来处理查询结果。如果查询过程中出现错误（err不为null），就调用reject()函数返回错误信息，表示获取商品图片列表失败，终止后续更新操作。
        dao.list("GoodPicModel", {"columns": {"goods_id": good.goods_id}}, function (err, oldpics) {
            if (err) return reject("获取商品图片列表失败");

            var batchFns = [];

            // 获取新的图片信息，如果info.pics存在，就将其赋值给newpics变量，否则将newpics初始化为空数组，这样后续可以统一对新图片进行相关处理。
            var newpics = info.pics? info.pics : [];
            // 使用_.keyBy()方法（通常来自lodash库，用于将数组转换为以某个属性为键的对象形式）将新图片数组newpics根据pics_id属性转换为以pics_id为键的对象形式，
            // 这样后续可以方便地通过pics_id来查找和判断新图片与旧图片之间的对应关系等情况，例如通过newpicsKV[pic.pics_id]来快速判断某张旧图片在新图片中是否存在对应的记录。
            var newpicsKV = _.keyBy(newpics, "pics_id");
            // 同样地，使用_.keyBy()方法将旧图片数组oldpics根据pics_id属性转换为以pics_id为键的对象形式，用于后续在新旧图片对比等操作中更方便地查找和判断相关情况。
            var oldpicsKV = _.keyBy(oldpics, "pics_id");

            /**
             * 保存图片集合
             * 以下代码块的主要作用是对新旧图片进行分类整理，根据一定的规则确定哪些图片需要新建、哪些需要保留以及哪些需要删除，
             * 以便后续针对不同分类的图片集合进行相应的数据库和文件系统操作，确保图片数据的更新符合业务逻辑要求。
             */
            // 需要新建的图片集合，后续会把新提交的但不存在对应pics_id的图片添加到这个集合中，然后对这些图片进行新建相关的一系列操作，比如生成合适的图片路径、插入数据库记录等操作。
            var addNewpics = [];
            // 需要保留的图片的集合，用于存放那些在新提交的图片数据中能找到对应pics_id的旧图片，意味着这些图片在本次更新操作中不需要进行删除等处理，而是继续保留使用，保持原有的图片关联和展示等功能。
            var reservedOldpics = [];
            // 需要删除的图片集合，用于存放那些在新提交的图片数据中找不到对应pics_id的旧图片，说明这些图片在本次更新后不再需要了，后续会对这些图片进行删除操作，包括从数据库中删除记录以及从文件系统中删除对应的图片文件等操作。
            var delOldpics = [];

            // 遍历旧图片数组oldpics，对于每一张旧图片pic，通过判断其pics_id在新图片对象newpicsKV中是否存在来确定该图片是要保留还是删除，
            // 如果newpicsKV[pic.pics_id]为真（即存在对应的pics_id），说明这张旧图片在新提交的数据中有对应记录，需要保留这张图片，将其添加到reservedOldpics集合中；
            // 如果不存在对应的pics_id，则说明这张旧图片在新提交的数据中已不再需要，将其添加到delOldpics集合中，表示需要删除这张图片。
            _(oldpics).forEach(function (pic) {
                if (newpicsKV[pic.pics_id]) {
                    reservedOldpics.push(pic);
                } else {
                    delOldpics.push(pic);
                }
            });

            // 遍历新图片数组newpics，对于每一张新图片pic，通过判断其是否不存在pics_id属性并且存在pic属性（通常pic属性可能表示图片的路径等关键信息，具体含义由业务逻辑决定）来确定该图片是否为新创建的图片，
            // 如果满足条件（即!pic.pics_id && pic.pic为真），说明这是一张新的需要创建记录的图片，将其添加到addNewpics集合中，以便后续对这些新图片进行创建相关的一系列操作，比如裁剪图片、设置合适的图片路径以及插入数据库记录等操作。
            _(newpics).forEach(function (pic) {
                if (!pic.pics_id && pic.pic) {
                    addNewpics.push(pic);
                }
            });

            // 开始处理商品图片数据逻辑
            // 1. 删除商品图片数据集合
            // 下面的代码会遍历需要删除的旧图片集合delOldpics，对每一张要删除的图片pic执行相关的删除操作，包括删除图片在文件系统中的物理路径以及在数据库中的数据记录。
            _(delOldpics).forEach(function (pic) {
                // 1.1 删除图片物理路径
                // 先通过path.join()方法结合当前工作目录（process.cwd()）和图片的大尺寸路径（pic.pics_big）构建完整的文件路径，
                // 然后将删除该大尺寸图片文件的操作（通过调用removeGoodPicFile函数实现）添加到batchFns数组中，batchFns数组用于收集所有需要批量执行的异步操作（比如文件删除、数据库记录删除、新图片创建等操作），后续会通过Promise.all统一执行这些操作并处理结果。
                batchFns.push(removeGoodPicFile(path.join(process.cwd(), pic.pics_big)));
                // 同样的方式，通过path.join()方法结合当前工作目录和图片的中等尺寸路径（pic.pics_mid）构建完整的文件路径，
                // 并将删除该中等尺寸图片文件的操作添加到batchFns数组中，确保所有不同尺寸的相关图片文件都能被删除，保持数据的一致性和完整性。
                batchFns.push(removeGoodPicFile(path.join(process.cwd(), pic.pics_mid)));
                // 按照上述方法，再结合当前工作目录和图片的小尺寸路径（pic.pics_sma）构建完整的文件路径，将删除该小尺寸图片文件的操作也添加到batchFns数组中，完成对要删除图片不同尺寸版本文件的删除操作准备工作。
                batchFns.push(removeGoodPicFile(path.join(process.cwd(), pic.pics_sma)));

                // 1.2 数据库中删除图片数据记录
                // 调用removeGoodPic函数来删除数据库中对应图片（pic）的记录信息，将这个删除数据库记录的操作添加到batchFns数组中，
                // 这样在后续通过Promise.all统一执行操作时，能保证图片在文件系统和数据库中的相关记录都能被正确删除，实现完整的图片删除逻辑，保持数据的一致性。
                batchFns.push(removeGoodPic(pic));
            });

            // 2. 处理新建图片的集合
            // 下面的代码会遍历需要新建的图片集合addNewpics，对每一张要新建的图片pic进行一系列相关操作，包括根据原始图片路径裁剪出不同尺寸的图片、设置图片的各尺寸路径信息、关联商品ID以及在数据库中创建新的图片数据记录等操作。
            _(addNewpics).forEach(function (pic) {
                if (!pic.pics_id && pic.pic) {
                    // 2.1 通过原始图片路径裁剪出需要的图片
                    // 首先通过path.join()方法结合当前工作目录（process.cwd()）和图片路径（pic.pic）构建原始图片的完整路径，赋值给src变量，用于后续的图片裁剪操作，确定从哪个原始图片文件进行裁剪处理。
                    var src = path.join(process.cwd(), pic.pic);
                    var tmp = src.split(path.sep);
                    var filename = tmp[tmp.length - 1];
                    // 为新图片构建大尺寸版本的存储路径，将文件名添加到固定的"/uploads/goodspics/big_"前缀后作为新的大尺寸图片路径，
                    // 同时更新pic对象的pics_big属性，使其记录新的大尺寸图片路径信息，方便后续保存裁剪后的大尺寸图片到指定位置，并且在数据库记录等操作中能准确使用该路径信息。
                    pic.pics_big = "/uploads/goodspics/big_" + filename;
                    // 同样的方式，为新图片构建中等尺寸版本的存储路径，将文件名添加到固定的"/uploads/goodspics/mid_"前缀后作为新的中等尺寸图片路径，
                    // 并更新pic对象的pics_mid属性，使其记录新的中等尺寸图片路径信息，确保后续在处理中等尺寸图片相关操作（如保存、展示等）时能使用正确的路径。
                    pic.pics_mid = "/uploads/goodspics/mid_" + filename;
                    // 以及为新图片构建小尺寸版本的存储路径，将文件名添加到固定的"/uploads/goodspics/sma_"前缀后作为新的小尺寸图片路径，
                    // 更新pic对象的pics_sma属性，使其记录新的小尺寸图片路径信息，保证图片不同尺寸版本的路径信息都准确记录在pic对象中，便于后续各种操作（如保存到文件系统、关联数据库记录以及在前端展示等）的顺利进行。
                    pic.pics_sma = "/uploads/goodspics/sma_" + filename;

                    // 将裁剪大尺寸图片的操作添加到batchFns数组中，调用clipImage函数进行裁剪操作，传入原始图片路径src、裁剪后要保存的大尺寸图片路径（path.join(process.cwd(),pic.pics_big)）以及期望的裁剪宽度800和高度800参数，
                    // 确保生成符合要求的大尺寸图片文件，后续会通过Promise.all统一执行该操作以及其他相关操作，并处理操作结果，保证整个新建图片的流程能正确执行，包括图片文件的生成和相关数据库记录的创建等操作。
                    batchFns.push(clipImage(src, path.join(process.cwd(), pic.pics_big), 800, 800));
                    // 同样将裁剪中等尺寸图片的操作添加到batchFns数组中，传入相应的中等尺寸图片的路径（path.join(process.cwd(),pic.pics_mid)）和期望尺寸（宽度400、高度400）参数，
                    // 进行中等尺寸图片的裁剪操作准备，保证能生成合适的中等尺寸图片文件，满足业务逻辑中对不同尺寸图片的需求，例如在前端以不同的展示效果呈现商品图片等情况。
                    batchFns.push(clipImage(src, path.join(process.cwd(), pic.pics_mid), 400, 400));
                    // 以及将裁剪小尺寸图片的操作添加到batchFns数组中，传入小尺寸图片的路径（path.join(process.cwd(),pic.pics_sma)）和期望尺寸（宽度200、高度200）参数，
                    // 完成对新图片不同尺寸版本裁剪操作的添加，以便后续统一执行裁剪和其他相关操作，确保新图片的各个尺寸版本都能正确生成，并且能与后续的数据库记录插入等操作配合，实现完整的新建图片逻辑，保证商品图片数据的完整性和准确性。
                    batchFns.push(clipImage(src, path.join(process.cwd(), pic.pics_sma), 200, 200));

                    // 将当前新建图片pic的goods_id属性设置为对应的商品goods_id（即good.goods_id），建立图片与商品的关联关系，
                    // 这样在将图片信息插入数据库时能准确对应到所属商品，确保数据的关联性和业务逻辑的正确性，方便后续在查询商品相关图片等操作时能准确获取到对应的图片信息。
                    pic.goods_id = good.goods_id;

                    // 2.2 数据库中新建数据记录
                    // 调用createGoodPic函数将包含新图片信息（已设置好尺寸路径、关联商品goods_id等属性）的pic对象插入到数据库中对应的商品图片表中，创建新的图片数据记录，
                    // 将这个插入数据库记录的操作添加到batchFns数组中，这样在后续通过Promise.all执行所有操作时，能保证新图片的文件创建（裁剪操作等）和数据库记录创建都能正确完成，实现完整的新建图片逻辑，保证商品图片数据在文件系统和数据库层面的一致性和准确性。
		
			// 如果没有任何图片操作就返回
优点
简单易懂：K-means算法的概念相对简单，易于理解和实现。
高效：对于大数据集，K-means算法通常能够相对快速地收敛到解，尤其是在使用适当的初始化方法（如k-means++）时。
适用于大规模数据：由于算法的效率，K-means可以处理包含大量数据点的数据集。
可扩展性：算法可以很容易地扩展到新的数据点，即当有新数据加入时，可以重新运行K-means算法或仅更新受影响的簇。
空间划分：K-means提供了一种将数据集划分为具有相似特征的空间区域的方法，这对于数据分析和可视化非常有用。
缺点
K值的选择：K-means算法的结果强烈依赖于K值的选择，即簇的数量。选择合适的K值通常是一个挑战，需要用户有一定的先验知识或尝试不同的K值。
对初始质心的敏感：K-means算法的性能和结果可能会受到初始质心选择的影响。如果初始质心选择不当，算法可能会收敛到局部最优解而不是全局最优解。
对噪声和异常值的敏感性：K-means算法对噪声和异常值非常敏感，因为它们会扭曲簇的形状并影响质心的位置。
不适用于非球形簇：K-means算法假设簇是球形的，即簇内点到质心的距离是相等的。如果簇的形状是非球形的，K-means可能无法正确识别簇的结构。
只能处理数值数据：K-means算法只能处理数值数据，对于分类数据或混合类型数据（数值和分类），需要进行额外的预处理。
结果的不稳定性：由于K-means算法对初始质心和噪声的敏感性，即使对于相同的数据集，每次运行算法也可能会得到不同的结果。
计算质心的局限性：K-means算法通过计算平均值来确定质心，这在某些情况下可能不是最佳的选择。例如，当数据具有非线性关系或簇的形状不规则时，平均值可能无法很好地代表簇的中心。
		// 3. 组装最新的数据挂载在“info”中“good”对象下
		dao.list("GoodPicModel",{"columns":{"goods_id":good.goods_id}},function(err,goodPics){

			if(err) return reject("获取所有商品图片列表失败");
			_(goodPics).forEach(function(pic){
				if(pic.pics_big.indexOf("http") == 0) {
					pic.pics_big_url = pic.pics_big;
				} else {
					pic.pics_big_url = upload_config.get("baseURL") + pic.pics_big;
					
				}
				
				if(pic.pics_mid.indexOf("http") == 0) {
					pic.pics_mid_url = pic.pics_mid;
				} else {
					pic.pics_mid_url = upload_config.get("baseURL") + pic.pics_mid;
					
				}
				if(pic.pics_sma.indexOf("http") == 0) {
					pic.pics_sma_url = pic.pics_sma;
				} else {
					pic.pics_sma_url = upload_config.get("baseURL") + pic.pics_sma;
					
				}
				
				// pic.pics_mid_url = upload_config.get("baseURL") + pic.pics_mid;
				// pic.pics_sma_url = upload_config.get("baseURL") + pic.pics_sma;
			});
			info.good.pics = goodPics;
			resolve(info);
		});
	});
}

/**
 * 挂载属性
 * @param  {[type]} info [description]
 * @return {[type]}      [description]
 */
function doGetAllAttrs(info) {
	return new Promise(function(resolve,reject){
		var good = info.good;
		if(!good.goods_id) return reject("获取商品图片必须先获取商品信息");
		goodAttributeDao.list(good.goods_id,function(err,goodAttrs){

			if(err) return reject("获取所有商品参数列表失败");
			info.good.attrs = goodAttrs;
			resolve(info);
		});
	});
}


/**
 * 创建商品
 * 
 * @param  {[type]}   params 商品参数
 * @param  {Function} cb     回调函数
 */
module.exports.createGood = function(params,cb) {


	// 验证参数 & 生成数据
	generateGoodInfo(params)
	// 检查商品名称
	.then(checkGoodName)
	// 创建商品
	.then(createGoodInfo)
	// 更新商品图片
	.then(doUpdateGoodPics)
	// 更新商品参数
	.then(doUpdateGoodAttributes)
	.then(doGetAllPics)
	.then(doGetAllAttrs)
	// 创建成功
	.then(function(info){
		cb(null,info.good);
	})
	.catch(function(err) {
		cb(err);
	});
}

/**
 * 删除商品
 * 
 * @param  {[type]}   id 商品ID
 * @param  {Function} cb 回调函数
 */
module.exports.deleteGood = function(id,cb) {
	if(!id) return cb("产品ID不能为空");
	if(isNaN(id)) return cb("产品ID必须为数字");
	dao.update(
		"GoodModel",
		id,
		{
			'is_del':'1',
			'delete_time':Date.parse(new Date()) / 1000,
			'upd_time' : Date.parse(new Date()) / 1000
		},
		function(err){
			if(err) return cb(err);
			cb(null);
		}
	);
}

/**
 * 获取商品列表
 * 
 * @param  {[type]}   params     查询条件
 * @param  {Function} cb         回调函数
 */
module.exports.getAllGoods = function(params,cb) {
	var conditions = {};
	if(!params.pagenum || params.pagenum <= 0) return cb("pagenum 参数错误");
	if(!params.pagesize || params.pagesize <= 0) return cb("pagesize 参数错误"); 

	conditions["columns"] = {};
	if(params.query) {
		conditions["columns"]["goods_name"] = orm.like("%" + params.query + "%");
	}
	conditions["columns"]["is_del"] = '0';


	dao.countByConditions("GoodModel",conditions,function(err,count){
		if(err) return cb(err);
		pagesize = params.pagesize;
		pagenum = params.pagenum;
		pageCount = Math.ceil(count / pagesize);
		offset = (pagenum - 1) * pagesize;
		if(offset >= count) {
			offset = count;
		}
		limit = pagesize;

		// 构建条件
		conditions["offset"] = offset;
		conditions["limit"] = limit;
		conditions["only"] = ["goods_id","goods_name","goods_price","goods_weight","goods_state","add_time","goods_number","upd_time","hot_mumber","is_promote","goods_introduce"];
		conditions["order"] = "-add_time";


		dao.list("GoodModel",conditions,function(err,goods){
			if(err) return cb(err);
			var resultDta = {};
			resultDta["total"] = count;
			resultDta["pagenum"] = pagenum;
			resultDta["goods"] = _.map(goods,function(good){
				return _.omit(good,"is_del","goods_big_logo","goods_small_logo","delete_time");
			});
			cb(err,resultDta);
		})
	});
}

/**
 * 更新商品
 * 
 * @param  {[type]}   id     商品ID
 * @param  {[type]}   params 参数
 * @param  {Function} cb     回调函数
 */
module.exports.updateGood = function(id,params,cb) {
	
	params.goods_id = id;
	// 验证参数 & 生成数据
	generateGoodInfo(params)
	// 检查商品名称
	.then(checkGoodName)
	// 创建商品
	.then(updateGoodInfo)
	// 更新商品图片
	.then(doUpdateGoodPics)
	// 更新商品参数
	.then(doUpdateGoodAttributes)
	.then(doGetAllPics)
	.then(doGetAllAttrs)
	// 创建成功
	.then(function(info){
		cb(null,info.good);
	})
	.catch(function(err) {
		cb(err);
	});
}

/**
 * 更新商品图片
 * 
 * @param  {[type]}   goods_id 商品ID
 * @param  {[type]}   pics     商品图片
 * @param  {Function} cb       回调函数
 */
module.exports.updateGoodPics = function(goods_id,pics,cb) {
	if(!goods_id) return cb("商品ID不能为空");
	if(isNaN(goods_id)) return cb("商品ID必须为数字");

	getGoodInfo({"goods_id":goods_id,"pics":pics})
	.then(doUpdateGoodPics)
	.then(doGetAllPics)
	.then(doGetAllAttrs)
	.then(function(info){
		cb(null,info.good);
	})
	.catch(function(err) {
		cb(err);
	});
}

module.exports.updateGoodAttributes = function(goods_id,attrs,cb) {
	
	getGoodInfo({"goods_id":goods_id,"attrs":attrs})
	.then(doUpdateGoodAttributes)
	.then(doGetAllPics)
	.then(doGetAllAttrs)
	.then(function(info){
		cb(null,info.good);
	})
	.catch(function(err) {
		cb(err);
	});
}

module.exports.updateGoodsState = function(goods_id,state,cb) {
	getGoodInfo({"goods_id":goods_id,"goods_state":state})
	.then(updateGoodInfo)
	.then(doGetAllPics)
	.then(doGetAllAttrs)
	.then(function(info){
		cb(null,info.good);
	})
	.catch(function(err) {
		cb(err);
	});
}

/**
 * 通过商品ID获取商品数据
 * 
 * @param  {[type]}   id 商品ID
 * @param  {Function} cb 回调函数
 */
module.exports.getGoodById = function(id,cb) {
	getGoodInfo({"goods_id":id})
	.then(doGetAllPics)
	.then(doGetAllAttrs)
	.then(function(info){
		cb(null,info.good);
	})
	.catch(function(err) {
		cb(err);
	});
}

/**
 * 创建商品
 * 
 * @param  {[type]}   params 商品参数
 * @param  {Function} cb     回调函数
 */
module.exports.createGood = function(params,cb) {


	// 验证参数 & 生成数据
	generateGoodInfo(params)
	// 检查商品名称
	.then(checkGoodName)
	// 创建商品
	.then(createGoodInfo)
	// 更新商品图片
	.then(doUpdateGoodPics)
	// 更新商品参数
	.then(doUpdateGoodAttributes)
	.then(doGetAllPics)
	.then(doGetAllAttrs)
	// 创建成功
	.then(function(info){
		cb(null,info.good);
	})
	.catch(function(err) {
		cb(err);
	});
}

/**
 * 删除商品
 * 
 * @param  {[type]}   id 商品ID
 * @param  {Function} cb 回调函数
 */
module.exports.deleteGood = function(id,cb) {
	if(!id) return cb("产品ID不能为空");
	if(isNaN(id)) return cb("产品ID必须为数字");
	dao.update(
		"GoodModel",
		id,
		{
			'is_del':'1',
			'delete_time':Date.parse(new Date()) / 1000,
			'upd_time' : Date.parse(new Date()) / 1000
		},
		function(err){
			if(err) return cb(err);
			cb(null);
		}
	);
}

/**
 * 获取商品列表
 * 
 * @param  {[type]}   params     查询条件
 * @param  {Function} cb         回调函数
 */
module.exports.getAllGoods = function(params,cb) {
	var conditions = {};
	if(!params.pagenum || params.pagenum <= 0) return cb("pagenum 参数错误");
	if(!params.pagesize || params.pagesize <= 0) return cb("pagesize 参数错误"); 

	conditions["columns"] = {};
	if(params.query) {
		conditions["columns"]["goods_name"] = orm.like("%" + params.query + "%");
	}
	conditions["columns"]["is_del"] = '0';


	dao.countByConditions("GoodModel",conditions,function(err,count){
		if(err) return cb(err);
		pagesize = params.pagesize;
		pagenum = params.pagenum;
		pageCount = Math.ceil(count / pagesize);
		offset = (pagenum - 1) * pagesize;
		if(offset >= count) {
			offset = count;
		}
		limit = pagesize;

		// 构建条件
		conditions["offset"] = offset;
		conditions["limit"] = limit;
		conditions["only"] = ["goods_id","goods_name","goods_price","goods_weight","goods_state","add_time","goods_number","upd_time","hot_mumber","is_promote","goods_introduce"];
		conditions["order"] = "-add_time";


		dao.list("GoodModel",conditions,function(err,goods){
			if(err) return cb(err);
			var resultDta = {};
			resultDta["total"] = count;
			resultDta["pagenum"] = pagenum;
			resultDta["goods"] = _.map(goods,function(good){
				return _.omit(good,"is_del","goods_big_logo","goods_small_logo","delete_time");
			});
			cb(err,resultDta);
		})
	});
}

/**
 * 更新商品
 * 
 * @param  {[type]}   id     商品ID
 * @param  {[type]}   params 参数
 * @param  {Function} cb     回调函数
 */
module.exports.updateGood = function(id,params,cb) {
	
	params.goods_id = id;
	// 验证参数 & 生成数据
	generateGoodInfo(params)
	// 检查商品名称
	.then(checkGoodName)
	// 创建商品
	.then(updateGoodInfo)
	// 更新商品图片
	.then(doUpdateGoodPics)
	// 更新商品参数
	.then(doUpdateGoodAttributes)
	.then(doGetAllPics)
	.then(doGetAllAttrs)
	// 创建成功
	.then(function(info){
		cb(null,info.good);
	})
	.catch(function(err) {
		cb(err);
	});
}

/**
 * 更新商品图片
 * 
 * @param  {[type]}   goods_id 商品ID
 * @param  {[type]}   pics     商品图片
 * @param  {Function} cb       回调函数
 */
module.exports.updateGoodPics = function(goods_id,pics,cb) {
	if(!goods_id) return cb("商品ID不能为空");
	if(isNaN(goods_id)) return cb("商品ID必须为数字");

	getGoodInfo({"goods_id":goods_id,"pics":pics})
	.then(doUpdateGoodPics)
	.then(doGetAllPics)
	.then(doGetAllAttrs)
	.then(function(info){
		cb(null,info.good);
	})
	.catch(function(err) {
		cb(err);
	});
}

module.exports.updateGoodAttributes = function(goods_id,attrs,cb) {
	
	getGoodInfo({"goods_id":goods_id,"attrs":attrs})
	.then(doUpdateGoodAttributes)
	.then(doGetAllPics)
	.then(doGetAllAttrs)
	.then(function(info){
		cb(null,info.good);
	})
	.catch(function(err) {
		cb(err);
	});
}

module.exports.updateGoodsState = function(goods_id,state,cb) {
	getGoodInfo({"goods_id":goods_id,"goods_state":state})
	.then(updateGoodInfo)
	.then(doGetAllPics)
	.then(doGetAllAttrs)
	.then(function(info){
		cb(null,info.good);
	})
	.catch(function(err) {
		cb(err);
	});
}

/**
 * 通过商品ID获取商品数据
 * 
 * @param  {[type]}   id 商品ID
 * @param  {Function} cb 回调函数
 */
module.exports.getGoodById = function(id,cb) {
	getGoodInfo({"goods_id":id})
	.then(doGetAllPics)
	.then(doGetAllAttrs)
	.then(function(info){
		cb(null,info.good);
	})
	.catch(function(err) {
		cb(err);
	});
}
