关于后台部分业务重构的思考及实践

关于后台部分业务重构的思考及实践

作者: ljmatlight
时间: 2017-09-25

积极主动,想事谋事,敢作敢为,能做能为。


当职以来,随着对公司业务和项目的不断深入,不断梳理业务和公司技术栈。
保证在完成分配开发任务情况下,积极思考优化方案并付诸实践。

一、想法由来

由于当前我司主要针对各大银行信用卡平台展开相关业务,
故不难看出,各银行信用卡平台虽然有各自的特性,
但其业务相似程度仍然很高,除必要的重复性工作外,仍有很大提升优化空间。
例如: 各个银行平台都需要对账工作、都要安排人力去开发重复类似的功能,
且不能很好地适应新的需求变化,修改耗时费力,可维护性较差。

二、业务分析

依托具体业务场景进行分析,每个平台都具有对账功能。
对账业务:
1、主要包括列表分页和导出功能
2、能够按照时间范围搜索
3、列表包括分页、金额统计、状态转换等等

优化依据:

  • 对特性业务进行差异性对待(如导出数据字段,结果转换字段等等),
  • 充分利用面向对象的思想进行合理的抽象层次建设

三、技术优化实践

后台技术栈为Jfinal,LayUI。

关于对账优化整体思路:

1、前端页面发起请求,传递响应参数

前端传递参数形式如下图:

PH.api2('#(base)/icbc/mall/compared/pay/list', {
    "comparedListBean.orderId": orderId,
    "comparedListBean.reqNo": reqNo,
    "comparedListBean.startTime": startTime,
    "comparedListBean.endTime": endTime,
    "comparedListBean.pageNo": page,
    "comparedListBean.pageSize": 20
}, function(res) {

采用bean类首写字母小写,加 ”.” 加 属性名称的形式进行书写。

2、定义dto 进行参数的bean 形式接受

由于所有列表,都包含起始搜索时间,当前页,每页显示数量,故定义基础列表dto的Bean 如下图所示:


/**
 * Description: 列表请求参数封装
 * <br /> Author: galsang
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class BaseListBean {

    private String startTime;

    private String endTime;

    private int pageNo = 1;

    private int pageSize = 20;

    private int start = (pageNo - 1) * pageSize;

}

根据具体业务可以扩展基础列表dto的Bean,
例如需要添加订单号、请求流水号,可创建Bean 继承基础bean进行扩展,如图:

/**
 * Description: 对账 - 列表请求参数封装
 * <br /> Author: galsang
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ComparedListBean extends BaseListBean {

    private String orderId;

    private String reqNo;

}

3、后端使用getBean 进行接收,根据需要对参数进行验证,并将Bean转换为Map

/**
 * 将接收参数的Bean 转换成 sqlMap
 *
 * @param modelClass Bean.class
 * @return
 * @throws BeanException
 */
public Map<String, Object> sqlMap(Class<?> modelClass) {
    try {
        return sqlMapHandler(BeanUtil.bean2map(getBean(modelClass)));
    } catch (BeanException e) {
        e.printStackTrace();
    }
    return null;
}

/**
 * 处理sql 参数数据
 * <br />
 *
 * @param sqlMap
 * @return
 */
private Map<String, Object> sqlMapHandler(Map<String, Object> sqlMap) {

    // 区别是导出还是列表
    if(null == sqlMap.get("start")){
        return sqlMap;
    }
    int pageNo = Integer.parseInt(String.valueOf(sqlMap.get("pageNo")));
    int pageSize = Integer.parseInt(String.valueOf(sqlMap.get("pageSize")));
    sqlMap.put("start", (pageNo - 1) * pageSize);
    return sqlMap;
}

如果需要对参数进行验证,则可以使用jfinal 验证Bean 的方法创建相应验证Bean。

4、将sql 语句统一写在md文件中

对账业务主要用到四种形式的sql, 故定义枚举进行统一的约定。

/**
 * 定义使用sql命名空间后缀
 */
enum NameSpaceSqlSuffix {

    LIST("查询列表", ".list"),
    COUNT("查询数量", ".count"),
    TOTAL("查询统计", ".total"),
    EXPORT("导出文件", ".export");

    private String name;

    private String value;

    NameSpaceSqlSuffix(String name, String value) {
        this.name = name;
        this.value = value;
    }

}

命名统一,可以直接定位需要实现或变动的需求,方便维护

5、结果数据转换接口

结果数据的的转换主要分为列表数据的转换和单条数据的转换,由于转换数据不一定相同,只要在具体的业务层进行定义内部类实现该接口run方法即可。

/**
 * Description: 结果类型数据转换接口
 * <br /> Author: galsang
 */
public interface IConvertResult {

    /**
     * 执行列表结果类型转换
     *
     * @param records
     */
    void run(List<Record> records);

    /**
     * 执行单个结果类型转换
     *
     * @param record
     */
    void run(Record record);

}

6、抽象公共方法

通用查询列表

/**
 * 查询并转换列表数据
 *
 * @param sql            查询列表数据sql
 * @param iConvertResult 数据转换
 * @return 转换后的列表数据
 */
public List<Record> doSqlAndResultConvert(String sql, IConvertResult iConvertResult) {
    List<Record> orders = dbPro.find(sql);
    iConvertResult.run(orders);
    return orders;
}

通过md命名空间查询列表信息

/**
 * 通用查询列表信息
 *
 * @param nameSpace      sql 文件的命名空间
 * @param sqlMap
 * @param iConvertResult
 * @return
 */
public Map<String, Object> listByNameSpace(String nameSpace, Map<String, Object> sqlMap, IConvertResult iConvertResult) {

    String sqlList = dbPro.getSqlPara(nameSpace + NameSpaceSqlSuffix.LIST.getValue(), sqlMap).getSql();
    String sqlCount = dbPro.getSqlPara(nameSpace + NameSpaceSqlSuffix.COUNT.getValue(), sqlMap).getSql();
    String sqlTotal = dbPro.getSqlPara(nameSpace + NameSpaceSqlSuffix.TOTAL.getValue(), sqlMap).getSql();
    int pageSize = Integer.parseInt(String.valueOf(sqlMap.get("pageSize")));

    return this.listBySql(sqlList, sqlCount, sqlTotal, pageSize, iConvertResult);
}

通过sql查询列表信息

/**
 * 通用查询列表信息
 *
 * @param sql            查询数据列表sql
 * @param countSql       查询统计数量sql
 * @param totalSql       查询统计总计sql
 * @param pageSize       每页显示长度
 * @param iConvertResult 结果类型装换实现类
 * @return 处理完成的结果数据
 */
public Map<String, Object> listBySql(String sql, String countSql, String totalSql, int pageSize, IConvertResult iConvertResult) {

    // 查询数据总量
    Long counts = dbPro.queryLong(countSql);

    // 查询统计数据
    Record total = null;
    if (StringUtil.isNotEmpty(totalSql)) {
        total = dbPro.findFirst(totalSql);
        iConvertResult.run(total);
    }

    // 查询列表数据并执行结果转换
    List<Record> orders = doSqlAndResultConvert(sql, iConvertResult);

    // 响应数据组织
    float pages = (float) counts / pageSize;
    Map<String, Object> resultMap = Maps.newHashMap();
    resultMap.put("errorCode", 0);
    resultMap.put("message", "操作成功");
    resultMap.put("data", orders);
    resultMap.put("totalRow", counts);
    resultMap.put("pages", (int) Math.ceil(pages));
    if (StringUtil.isNotEmpty(totalSql)) {
        resultMap.put("total", total);
    }
    return resultMap;
}

进行数据库查询;
对查询结果数据进行转换;
响应数据的组织。

查询导出文件数据

/**
 * 导出文件
 * @param nameSpace
 * @param sqlMap
 * @param iConvertResult
 * @return
 */
public List<Record> exportByNameSpace(String nameSpace, Map<String, Object> sqlMap, IConvertResult iConvertResult) {
    // 要导出的数据信息(已经转换)
     return doSqlAndResultConvert(dbPro.getSqlPara(nameSpace + NameSpaceSqlSuffix.EXPORT.getValue(), sqlMap).getSql(),
            iConvertResult);
}

7、具体业务层实现

支付对账业务层

/**
 * Description: 对账 - 支付业务层
 * <br /> Author: galsang
 */
public class ComparedPayService extends BaseService {

    public static final String MARKDOWN_SQL_NAMESPACE = "mall_compared_pay";

    /**
     * 查询信息列表
     *
     * @param sqlMap 查询条件
     * @return 响应结果数据
     */
    public Map<String, Object> list(Map<String, Object> sqlMap) {
        return super.listByNameSpace(MARKDOWN_SQL_NAMESPACE, sqlMap, new ComparedPayConvertResult());
    }

继承基础抽象业务BeseService;
定义具体业务层使用的sql命名空间常量;
查询信息列表。

实现 IConvertResult 接口

/**
 * 结果类型装换实现类
 */
private final class ComparedPayConvertResult extends AbstractConvertResult {

}

由于支付对账和退款对账转换数据相同,故定义抽象转换类

/**
 * Description:
 * <br /> Author: galsang
 */
public abstract class AbstractConvertResult implements IConvertResult {

    List<Record> goodExts = Db.use("superfilm").find(" SELECT id, color FROM mall_good_ext ");

    @Override
    public void run(List<Record> orders) {
        orders.forEach(o -> {
            o.set("companyAmt", o.getInt("amount") - o.getInt("payAmount"));
            RecordUtil.sqlToJavaAmount(o, "amount", "payAmount", "pointAmt", "totalDiscAmt", "companyAmt");
            o.set("style", getStyle(o.getInt("goodExtId")));
            o.set("statusCN", MallOrderStatus.reasonPhraseByStatusCode(o.getInt("status")));
        });
    }

    @Override
    public void run(Record record) {
        record.set("totalCompanyAmt", record.getInt("totalAmount") - record.getInt("totalPayAmount"));
        RecordUtil.sqlToJavaAmount(record, "totalAmount", "totalPayAmount", "totalPointAmt", "totalTotalDiscAmt");
    }

    /**
     * 获取商品规格
     *
     * @param goodExtId 商品详情id
     * @return 商品规格
     */
    public String getStyle(final int goodExtId) {
        Iterator<Record> iterator = goodExts.iterator();
        while (iterator.hasNext()) {
            Record record = iterator.next();
            if (record.getInt("id").intValue() == goodExtId) {
                return record.getStr("color");
            }
        }
        return "没有对应规格或已下架";
    }
}

生成导出文件

/**
 * 生成导出文件
 *
 * @param sqlMap         查询条件
 * @param fileSuffixName 生成文件名称后缀
 * @param sheetName      工作表标题名称
 * @return 要导出的文件对象
 * @throws IOException
 * @throws URISyntaxException
 */
public File export(Map<String, Object> sqlMap, String fileSuffixName, String sheetName) throws IOException, URISyntaxException {

    // TODO 需要切换sql 命名空间, 和 结果转换类
    List<Record> records = super.exportByNameSpace(MARKDOWN_SQL_NAMESPACE, sqlMap, new ComparedPayConvertResult());

    // 执行相应的导出操作
    Workbook wb = new XSSFWorkbook();

    // TODO 必须定制化操作
    this.doSheet(wb, records, sheetName);

    return ExportPoiUtil.createExportFile(wb, fileSuffixName);
}

由于导出文件字段的差异性,所以必须根据具体业务对相应的字段和数据进行修改。


/**
 * 填充工作表数据
 *
 * @param wb         表格对象
 * @param recordList 填充列表数据信息
 * @param sheetName  工作表名称
 */
private void doSheet(Workbook wb, List<Record> recordList, String sheetName) {
    // 创建工作表 - 并制定工作表名称
    Sheet sheet = wb.createSheet(WorkbookUtil.createSafeSheetName(sheetName));
    short rowNum = 0;  // 设置初始行号
    Row row = sheet.createRow(rowNum++); // 创建表格标题行
    ExportPoiUtil.header(wb, row, "序号", "订单号", "请求流水号", "商品", "商品规格", "数量", "总金额",
            "清算", "积分抵扣", "行内优惠", "公司补贴", "支付时间", "状态");
    int serNo = 1; // 填充表格数据行
    for (Record order : recordList) {
        int columnNum = 0;
        JSONObject json = new JSONObject();
        json.put("amount", order.getBigDecimal("amount"));
        json.put("payAmount", order.getBigDecimal("payAmount"));
        json.put("pointAmt", order.getBigDecimal("pointAmt"));
        json.put("totalDiscAmt", order.getBigDecimal("totalDiscAmt"));
        json.put("companyAmt", order.getBigDecimal("amount").subtract(order.getBigDecimal("payAmount")));

        row = sheet.createRow(rowNum++);
        row.createCell(columnNum++).setCellValue(serNo++);
        row.createCell(columnNum++).setCellValue(order.getStr("orderId"));
        row.createCell(columnNum++).setCellValue(order.getStr("reqNo"));
        row.createCell(columnNum++).setCellValue(order.getStr("goodName"));
        row.createCell(columnNum++).setCellValue(order.getStr("style"));
        row.createCell(columnNum++).setCellValue(order.getStr("count"));
        row.createCell(columnNum++).setCellValue(json.getDouble("amount"));
        row.createCell(columnNum++).setCellValue(json.getDouble("payAmount"));
        row.createCell(columnNum++).setCellValue(json.getDouble("pointAmt"));
        row.createCell(columnNum++).setCellValue(json.getDouble("totalDiscAmt"));
        row.createCell(columnNum++).setCellValue(json.getDouble("companyAmt"));
        row.createCell(columnNum++).setCellValue(new JDateTime(order.getDate("createdTime")).toString("YYYY-MM-DD hh:mm:ss"));
        row.createCell(columnNum++).setCellValue(order.getStr("statusCN"));
    }

}

8、工具类

由于当前系统精确到分,数据库中以int存储分,但是前端显示的时候要求显示元,故可使用此工具类进行“分”到“元”的转换处理。

/**
 * Description: 记录对象相关工具类
 * <br /> Author: galsang
 */
@Slf4j
public class RecordUtil {

    /**
     * 数据库中保存的金额(分)转换为金额(元)
     *
     * @param record 记录对象
     * @param key    字段索引
     */
    public static void sqlToJavaAmount(Record record, String... key) {
        if (record != null) {
            int keyLength = key.length;
//            log.info(" keyLength ================ " + keyLength);
            for (int i = 0; i < keyLength; i++) {
//                log.info(" key[" + i + "] ================ " + key[i]);
                if (record.getInt(key[i]) != null) {
                    record.set(key[i], new BigDecimal(record.getInt(key[i])).divide(BigDecimal.valueOf(100)));
                }else{
                    record.set(key[i], new BigDecimal(0));
                }
            }
        }
    }

}

文件导出工具类

/**
 * @Description: 导出POI文件工具类
 * @Author: galsang
 * @Date: 2017/7/7
 */
public class ExportPoiUtil 

具体代码参见后台对账业务实现。

9、几点约定

  1. 前端: startTime 、endTime、pageNo、pageSize、
  2. md – sql命名空间后缀 : list、count、total、export

四、交流提高

不足之处,还请各位同事多多指教,谢谢。



同时经过调整最终形成以下基础业务层代码。

BaseService 代码如下:



/**
 * 基础业务层封装
 *
 * @author ljmatlight
 * @date 2017/10/17
 */
@Slf4j
public abstract class BaseService {

    /**
     * 由子类提供具体数据源=
     *
     * @return
     */
    protected abstract DbPro dbPro();

    /**
     * 由子类提供具体 sql 命名空间
     *
     * @return
     */
    protected abstract String sqlNameSpace();

    /**
     * 由子类提供具体结果数据转换
     *
     * @return
     */
    protected abstract IConvertResult iConvertResult();

    /**
     * 通用查询列表信息
     *
     * @param sql            查询数据列表sql
     * @param countSql       查询统计数量sql
     * @param totalSql       查询统计总计sql
     * @param pageSize       每页显示长度
     * @param iConvertResult 结果类型装换实现类
     * @return 处理完成的结果数据
     */
    private Map<String, Object> listBySql(String sql, String countSql, String totalSql, int pageSize, IConvertResult iConvertResult) {

        // 查询数据总量
        Long counts = this.dbPro().queryLong(countSql);

        // 查询列表数据并执行结果转换
        List<Record> orders = doSqlAndResultConvert(sql, iConvertResult);

        // 响应数据组织
        float pages = (float) counts / pageSize;
        Map<String, Object> resultMap = Maps.newHashMap();
        resultMap.put("errorCode", 0);
        resultMap.put("message", "操作成功");
        resultMap.put("data", orders);
        resultMap.put("totalRow", counts);
        resultMap.put("pages", (int) Math.ceil(pages));

        // 查询统计数据
        if (StringUtil.isNotEmpty(totalSql)) {
            Record total = this.dbPro().findFirst(totalSql);

            if (iConvertResult != null) {
                iConvertResult.run(total);
            }
            resultMap.put("total", total);
        }
        return resultMap;
    }

    /**
     * 通用查询列表信息
     *
     * @param nameSpace      sql 文件的命名空间
     * @param sqlMap         sql参数
     * @param iConvertResult
     * @return
     */
    protected Map<String, Object> listByNameSpace(String nameSpace, Map<String, Object> sqlMap, IConvertResult iConvertResult) {

        String sqlList = this.dbPro().getSqlPara(nameSpace + NameSpaceSqlSuffix.LIST.getValue(), sqlMap).getSql();
        String sqlCount = this.dbPro().getSqlPara(nameSpace + NameSpaceSqlSuffix.COUNT.getValue(), sqlMap).getSql();

        String sqlTotal = null;

        try {
            sqlTotal = this.dbPro().getSqlPara(nameSpace + NameSpaceSqlSuffix.TOTAL.getValue(), sqlMap).getSql();
        } catch (Exception e) {
            log.info("sqlTotal === 没有统计相关 sql");
        }

        int pageSize = Integer.parseInt(String.valueOf(sqlMap.get("pageSize")));

        return this.listBySql(sqlList, sqlCount, sqlTotal, pageSize, iConvertResult);
    }

    /**
     * 查询并转换列表数据
     *
     * @param sql            查询列表数据sql
     * @param iConvertResult 数据转换
     * @return 转换后的列表数据
     */
    private List<Record> doSqlAndResultConvert(String sql, IConvertResult iConvertResult) {

        List<Record> orders = this.dbPro().find(sql);

        if (iConvertResult != null) {
            iConvertResult.run(orders);
        }

        return orders;
    }

    /**
     * 导出文件
     *
     * @param nameSpace
     * @param sqlMap
     * @param iConvertResult
     * @return
     */
    private List<Record> exportByNameSpace(String nameSpace, Map<String, Object> sqlMap, IConvertResult iConvertResult) {
        // 要导出的数据信息(已经转换)
        return doSqlAndResultConvert(this.dbPro().getSqlPara(nameSpace + NameSpaceSqlSuffix.EXPORT.getValue(), sqlMap).getSql(),
                iConvertResult);
    }

    /**
     * 查询信息列表
     *
     * @param sqlMap 查询条件
     * @return 响应结果数据
     */
    public Map<String, Object> list(Map<String, Object> sqlMap) {
        log.info("this.sqlNameSpace() ============= " + this.sqlNameSpace());
        return this.listByNameSpace(this.sqlNameSpace(), sqlMap, this.iConvertResult());
    }

    /**
     * 生成导出文件
     *
     * @param sqlMap         查询条件
     * @param fileSuffixName 生成文件名称后缀
     * @param sheetName      工作表标题名称
     * @return 要导出的文件对象
     * @throws IOException
     * @throws URISyntaxException
     */
    public File export(Map<String, Object> sqlMap, String fileSuffixName, String sheetName) throws IOException, URISyntaxException {

        // 需要切换sql 命名空间, 和 结果转换类
        List<Record> records = this.exportByNameSpace(this.sqlNameSpace(), sqlMap, this.iConvertResult());

        // 执行相应的导出操作
        Workbook wb = new XSSFWorkbook();

        // 必须定制化操作
        this.doSheet(wb, records, sheetName);

        return ExportPoiUtil.createExportFile(wb, fileSuffixName);
    }

    /**
     * 由子类提供具体处理装换的数据
     *
     * @param wb
     * @param recordList
     * @param sheetName
     */
    protected abstract void doSheet(Workbook wb, List<Record> recordList, String sheetName);

    /**
     * 定义使用sql命名空间后缀
     */
    enum NameSpaceSqlSuffix {

        LIST("查询列表", ".list"), COUNT("查询数量", ".count"), TOTAL("查询统计", ".total"), EXPORT("导出文件", ".export");

        private String name;

        private String value;

        NameSpaceSqlSuffix(String name, String value) {
            this.name = name;
            this.value = value;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }
    }

}

五、成绩

在后续业务开展过程中,此基础业务层代码封装发挥了较好的作用,
大大缩短了开发时间,提高了工作效率,同时也提高了程序的易维护性。

原文地址:https://www.cnblogs.com/ljmatlight/p/9050747.html

时间: 2024-09-29 23:58:58

关于后台部分业务重构的思考及实践的相关文章

微信后台异步消息队列的优化升级实践分享

1.引言 MQ 异步消息队列是微信后台自研的重要组件,广泛应用在各种业务场景中,为业务提供解耦.缓冲.异步化等能力.本文分享了该组件2.0版本的功能特点及优化实践,希望能为类似业务(比如移动端IM系统等)的消息队列设计提供一定的参考. 2.关于分享者 廖文鑫,2013年加入腾讯,从事微信后台基础功能及架构的开发和运营,先后参与了消息通知推送系统.任务队列组件.春晚摇红包活动等项目,在海量分布式高性能系统方面有丰富的经验. 3.背景介绍 微信后台给件 MQ 1.0 发布之初,基本满足了一般业务场景

中国联通SDN/NFV的思考与实践

编者按:2015中国SDN/NFV大会在北京召开,本次大会围绕SDN/NFV展开讨论,来自运营商.服务提供商等业界巨头纷纷参与此次大会.中国联通技术部技术战略处经理裴小燕发表了题为<中国联通SDN/NFV的思考与实践>的演讲. 大家中午好!我简单的介绍,汇报一下中国联通对SDN/NFV的思考.我是制订公司发展战略的工作,因此借用网络上流行的话"世界很大,我们要去看看".我汇报的技术层面东西比较少,主要分享一下,我们去世界看看的一些结果. 关于运营商对于NFV的一些需求,我不

腾讯IVWEB前端工程化工具feflow思考与实践

本篇文章主要介绍腾讯IVWEB团队从0到1在工程化的思考和实践.feflow的全称是Front-end flow(前端工作流),致力于提升研发效率和规范的工程化解决方案.愿景是通过feflow,可以使项目创建.开发.构建.规范检查到最终项目上线的整个过程更加自动化和标准化. 要解决的问题 项目的目录结构按约定生成 团队有一套开发规范进行约束 支持多种类型的构建,包括Fis构建和webpack构建 团队内部的代码贡献统计.离线包内置App等 为了解决上述问题,我们于17年2月底开始投入工程化fef

前后端分离的思考与实践(六)

原文出处: 淘宝UED - 筱谷 Nginx + Node.js + Java 的软件栈部署实践 起 关于前后端分享的思考,我们已经有五篇文章阐述思路与设计.本文介绍淘宝网收藏夹将 Node.js 引入传统技术栈的具体实践. 淘宝网线上应用的传统软件栈结构为 Nginx + Velocity + Java,即: 在这个体系中,Nginx 将请求转发给 Java 应用,后者处理完事务,再将数据用 Velocity 模板渲染成最终的页面. 引入 Node.js 之后,我们势必要面临以下几个问题: 技

iOS并发编程笔记,包含GCD,Operation Queues,Run Loops,如何在后台绘制UI,后台I/O处理,最佳安全实践避免互斥锁死锁优先级反转等,以及如何使用GCD监视进程文件文件夹,并发测试的方案等

iOS并发编程笔记,包含GCD,Operation Queues,Run Loops,如何在后台绘制UI,后台I/O处理,最佳安全实践避免互斥锁死锁优先级反转等,以及如何使用GCD监视进程文件文件夹,并发测试的方案等 线程 使用Instruments的CPU strategy view查看代码如何在多核CPU中执行.创建线程可以使用POSIX 线程API,或者NSThread(封装POSIX 线程API).下面是并发4个线程在一百万个数字中找最小值和最大值的pthread例子: #import

关于智能平台中业务的CURD思考

为了修改一个分子公司的业务逻辑,修改大量的SQL,真的是蛋疼至极,而且公司现有智能平台的SQL编辑器,居然什么功能都没!完全就一个框框~别说什么调试和跟踪了. 回到自己研发的智能平台项目中来,如果一张业务表更新的同时还要更新其他业务表,并且还包含了各种列的取值算法.这个时候该怎么处理,到底是把CURD放到后台配置成手写SQL,还是通过后台拖拽关联?带着这个思考来看个业务场景 创建财务凭证 录入基础数据到凭证头,录入凭证明细 这时只有一个动作 更新UI  每增加一行明细,就把明细金额汇总到凭证头的

从业务角度出发思考

“从业务的角度出发去思考问题”是靖说的最多的一句话,也是我在momenta实习期间思考最多的一个问题(当然也是我被jing说的最多的一个点) 那么如何从业务角度出发去思考? 最重要的是理解业务.而业务是一群人共同协作完成的,为了理解业务,就需要和不同部门的人去聊,去探索业务里面涉及的每一个环节的具体的细节. ICC(Inside cabin camera)的数据流程是不同部门的人去合作完成的事情. 数据运营部门负责根据客户需求去配备好相应的人员,比如花钱去雇佣一些人到采集车上按照要求被摄像机拍摄

优化与重构的思考

看这篇文章:http://www.cnblogs.com/greyzeng/p/4077732.html 对评论引发我的思考. 网上有人说这句话我赞同: 优化和重构是两个概念啊,楼主还是没有搞清楚优化不宜过早主要指的是性能的优化不宜过早,因为很多性能优化其实没有对系统有明显的提升.而重构主要指的是修正代码中不好的味道,提高代码的可读性和可扩展性 优化的确不宜过早,但是重构是应该持续在整个开发过程中的当需求比较稳定的时候,就应该考虑通过重构来整理代码 另外一个人的观点: 我们的做法是,将重构这件事

业务重构

在重构一个结构繁杂,代码逻辑千丝万缕的业务系统时,除了对代码层面的重构之外,很多人会忽视对于业务结构的重构和简化. 目前正在遭遇着这个事情,一个异常复杂的系统,不断的在上面添加需求,代码量增大,函数的体积也在增长,Web服务也越来越臃肿.关于代码层面的解耦,方法论很多,但本质上就是"提取公因式",即相同的代码不要写两遍.通常,良好模块的模块设计,很容易达成这种只写一次的目标. 还有的复杂度就是模块之间的依赖和调用,很多人就会想到,用消息队列去解耦.其实消息队列解耦只是在技术层面,把两个