3

2019年是我变化最大的一年,不仅仅是技术,沟通交流与能力等各方面更让我清晰的认识到了自己的不足之处,学习的路还有很长很长,我有必要写出一篇文章来总结自己的这一年,以怀念我的2019。

入职

我是3月份加入的这家公司(四川领梵数字科技有限公司),然后直到现在,参与公司的核心产品发布后台管理系统与发布系统小程序的开发以及维护,也包含测试工作。

起初,我对公司的产品也是一知半解,我的老大给我讲的时候,包括我接任的前端开发的那位大哥也给我讲过。但那个时候,我还是一直处于懵懵懂懂的,隐约知道公司的产品是做展厅的,而我加入进来,主要也是参与发布系统外接一些外包项目。

但是,我隐约知道一点,就是需要学习pixi.js,这是一款编写2D精灵渲染的引擎。我进来的第一项工作就是熟悉已经写好的架构系统代码,随着开发的深入,我对这个产品也渐渐熟悉了。

开发小程序

3月到4月一个月的时间我主要就是熟悉发布后台管理系统的代码,以及维护一些和添加一些新功能,4月份到5月份,主要开发了发布系统的小程序的第一版,由于没有设计,尽管基本要求的功能完成了,而且花的时间也不多,也就一个星期的时间,所以第一版小程序就这么直接废弃了。

第二个星期,开发第二版小程序,这一版有了设计,经过一个月的时间开发和维护,基本功能也完成了,并且上线成功运营当中。

小程序的模块不算多,大致分为如下模块:

  • 登录:账号与密码登录与微信授权登录
  • 首页:播放计划以及相关操作(增删改以及发布终端),每个播放计划包含多个节目(节目操作包含增删 改),节目也可以预览和修改播放时间。(注:每个节目包含多个制作的模板)
  • 用户:安全中心,反馈,添加终端与激活终端。

这其中涉及到的基本功能也都包含到了,包括微信授权,支付,扫一扫,短信验证,数据的加密(后台做)与解密(前端做)。

小程序所用到的技术:uni-app。

图标是采用设计给的,布局是手写的样式,没有用到插件。

学到了什么?:参考了iview的row与col组件以及model组件的源码,在此基础上也自己封装了row和col组件以及model组件运用到了小程序当中。详情代码展示如下:

util.js:

/*
 * 功能:常用函数
 * 作者:eveningwater
 * 日期:2019/3/6
 */
// 往上查找组件的父组件
export function findComponentUp(context,componentName,componentNames){
    componentNames = typeof componentName === 'string' ? [componentName] : componentName;
    let parent = context.$parent;
    let name = parent.$options.name;
    //如果父组件不是传入的组件名,则循环往上查找,直到找到父组件为传入的组件名为止
    while(parent && (!name || componentName.indexOf(name) === -1)){
        parent = parent.$parent;
        if(parent)name = parent.$options.name;
    }
    return parent;
}
// 往下查找组件的子组件
export function findComponentDown(context,componentName){
    const childrens = context.$children;
    let children = null;
    if(childrens.length){
        // 循环遍历数组
//         for(const child of childrens){
//             const name = child.$options.name;
//             if(name === componentName){
//                 children = child;
//                 break;
//             }else{
//                 children = findComponentDown(child,componentName);
//                 if(children)break;
//             }
//         }
        for(let k = 0,len = childrens.length; k < len;k++){
            const name = childrens[k].$options.name;
            if(name === componentName){
                children = childrens[k];
                break;
            }else{
                children = findComponentDown(childrens[k],componentName);
                if(children)break;
            }
        }
    }
    return children
}
// 查找组件的所有父组件
export function findComponentsUp(context,componentName){
    let parents = [];
    if(parent){
        const name = parent.$options.name;
        if(name === componentName)parents.push(parent);
        return parents.concat(findComponentsUp(parent,componentName));
    }else{
        return [];
    }
}
// 查找组件的所有子组件
export function findComponentsDown(context, componentName) {
    let components = [];
    return context.$children.forEach((child) => {
        if (child.$options.name === componentName) components.push(child);
        let foundChild = findComponentsDown(child, componentName);
        return components.concat(foundChild);
    })
}
// 查找组件的兄弟组件
export function findComponentsBrother(context, componentName, exceptSelf = true) {
    // 找到当前组件的父组件的所有子组件
    let childComponents = context.$parent.$children.filter((item) => {
        return item.$options.name === componentName;
    })
    // 所有子组件中包含自身组件的索引
    let selfIndex = childComponents.findIndex((item) => {
        return context._uid === item._uid;
    })
    // 是否删除自身组件
    if (exceptSelf) childComponents.splice(selfIndex, 1);
    return childComponents;
}

row.vue:

<template>
    <div :style="gutterObject" class="ew-row">
        <slot></slot>
    </div>
</template>

<script>
    import {findComponentDown,findComponentsBrother} from './util.js'
    export default {
        props:{
            gutter:{
                type:[String,Number],
                default:0
            }
        },
        data() {
            return {
                
            };
        },
        computed:{
            // 间隔绑定
            gutterObject(){
                let gutter = parseInt(this.gutter),style = {};
                if(gutter){
                    style = 'margin-left:'+ gutter / -2 + 'px;' + 
                    'margin-right:' + gutter / -2 + 'px;';
                }
                return style;
            }
        },
        mounted(){
            
        },
        methods:{
            updateGutter(gutter){
                // 找到当前组件的col组件
                let ewCol = findComponentDown(this,'ewCol');
                let ewCols = findComponentsBrother(ewCol,'ewCol',false);
                if(ewCols.length){
                    ewCols.forEach((child) => {
                        if(gutter){
                            child.gutter = gutter;
                        }
                    })
                }
            }
        },
        watch:{
            'gutter':{
                handler(val){
                    if(val){
                        // 如果gutter不为0,向row组件下的col组件传递gutter值
                        this.updateGutter(val)
                    }
                },
                deep:true
            }
        }
    }
</script>

<style>
    @import './component.css';
</style>

col.vue:

<template>
    <div :style="gutterObject" :class="classObject" class="ew-col">
        <slot></slot>
    </div>
</template>

<script>
    import { findComponentUp } from './util.js'
    export default {
        props: {
            span: {
                type: [String, Number],
                default: 0
            }
        },
        data() {
            return {
                gutter:0
            };
        },
        computed: {
            // 区块间隔
            gutterObject(){
                let gutter = parseInt(this.gutter);
                if(gutter){
                    return 'padding-left:' + gutter / 2 + 'px;' +
                    'padding-right:' + gutter / 2 + 'px;';
                }
            },
            // 栅格类绑定
            classObject() {
                let span = parseInt(this.span);
                if (span) {
                    return 'ew-col-span-' + span;
                }
            }
        },
        methods:{
            updateGutter(){
                const ewRow = findComponentUp(this,'ewRow');
                if(ewRow){
                    ewRow.updateGutter(ewRow.gutter);
                }
            }
        },
        mounted() {
            this.updateGutter();
        }
    }
</script>

<style>
    @import './component.css';
</style>

model.vue:

<template>
    <view class="modal-mask" @click="$emit('on-ew-close',$event)" :class="className">
        <view class="modal-content" @click="$emit('on-ew-open',$event)">
            <text class="modal-title" v-if="message.title">{{ message.title }}</text>
            <text class="modal-content-text" v-if="message.content">{{ message.content }}</text>
            <slot name="content"></slot>
            <button type="primary" @click="$emit('on-ew-sure')" class="surebtn" v-if="!showCancel">确定</button>
            <ew-row v-else>
                <ew-col span="12">
                    <view @click="$emit('on-ew-sure')" class="surebtn">确定</view>
                </ew-col>
                <ew-col span="12">
                    <view @click="$emit('on-ew-cancel')" class="cancelbtn" >取消</view>
                </ew-col>
            </ew-row>
        </view>
    </view>
</template>

<script>
    export default {
        props:{
            message:{
                type:Object,
                default:() => {
                    return {}
                }
            },
            showCancel:{
                type:Boolean,
                default:false
            },
            className:String
        },
        computed:{
            
        },
        data() {
            return {

            };
        },
        mounted(){
            
        },
        methods:{
            
        }
    }
</script>

<style>
    @import './component.css';
</style>

遇到的问题:在开发当中我遇到了uni-app框架还不是完全支持vue插槽语法。我后面只能将不生效的组件重写一遍。

学习pixi.js

后面因为要熟悉老大写的引擎代码,所以我就花了一周时间将pixi.js基础学习了一下,并趁着不是太忙的时候,记录了下来,写成了自己的笔记。文档内容

发布系统的一些特色功能

对于发布系统,我还是挺自豪的,因为里面涉及到了很多功能都比较复杂,这其中包括组件事件编辑器,动画时间轴,素材库,动画设置等。

先来说明一下发布系统吧,这个产品有些类似易企秀的产品,但与易企秀又有着区别。而且发布系统的主要特色就是可以编辑粒子动画。

目前一些基本的编辑模板需要用到的组件都开发完成了,也能制作出一些模板。如以下就是我自己使用发布系统所制作出的模板。

后期也可以针对需求来完成一些特色的功能,这也是与易企秀的区别所在。

制作模板所用的引擎就是使用pixi.js编写的。发布系统的代码也是恐怖,就单单一个编辑模板里面所涉及到的代码就有二十多个文件,(PS:本来是没有这么多个文件的,全部代码集中在一个文件中,一个文件有几万行代码,我看着头疼,就一步步的将代码抽离出来,所以花的时间也比较多)。下面展示一下时间轴代码,其它就不展示了。如下图(只展示了其中一个文件夹的文件)所示:

时间轴代码:

timeline.vue:

<template>
    <div class="timeline" v-drag ref="timeline" :style="closeStyle">
        <!--让时间轴拖拽的元素 -->
        <div class="timeline-drag-el"></div>
        <!-- 关闭时间轴 -->
        <el-tooltip effect="dark" content="关闭" placement="top-start">
            <i :class="iconClassName" class="close-timeline" @click="closeTimeLine"></i>
        </el-tooltip>
        <div class="timeline-content-container" :style="closeStyle">
            <!-- 组件管理 -->
            <div class="timeline-component-group">
                    <div class="timeline-component-header">组件名</div>
                    <div class="timeline-component-name" v-for="(com,index) in componentArr" :key="index" :style ="curComponentIndex === index ?    'background-color:rgb(155, 199, 226);color:#fff;' : ''" @click="selectComponent(com,index)">
                        {{ com.styles.name }}
                        <el-tooltip effect="dark" content="播放" placement="top-start">
                             <i class="el-icon-caret-right play-component-icon" @click="playComponentAnimation(com)"></i>
                        </el-tooltip>
                         <el-tooltip effect="dark" content="删除" placement="top-start">
                             <i class="el-icon-delete delete-component-icon" @click="deleteComponent(com)"></i>
                        </el-tooltip>
                    </div>
            </div>
            <div class="timeline-timeline-container" ref="timeLineScroll">
                <!-- 模拟时间轴横向滚动条 -->
                <div class="timeline-scrollbar-wrapper">
                    <div class="timeline-scrollbar" style="width:1479px;">
                        <div class="timeline-track" style="width:1479px;">
                            <div class="timeline-thumb" style="width:990px;" :style="moveLeft" @mousedown="changeLeft"></div>
                        </div>
                    </div>
                </div>
                <div class="timeline-container">
                    <div 
                        class="timeline-content-overview" 
                        ref="timeLineView" 
                        :style="{ 'min-width':'100%',width:spotArr * 195 + 22 + 'px',left:-viewLeft + 'px'}">
                        <!-- 时间轴刻度线 -->
                        <div class="timeline-content-iframe">
                            <div class="timeline-content-marker" v-for="(t,index) in spotArr" :key="index" :style="{ width:'195px'}">
                                <span class="timeline-text">{{ index + 's' }}</span>
                            </div>
                        </div>
                        <!-- 时间管理 -->
                        <div class="timeline-layer-container">
                            <div class="timeline-layer-area" v-for="(com,index) in componentArr" :key="index" :style ="curComponentIndex === index ?    'background-color:rgb(155, 199, 226);color:#fff;' : ''">
                                <div class="timeline-layer-animation"
                                    v-for = "(node,_index) in com['animation']['group'][0].ani"
                                    :key="_index"
                                    @mousedown="changeDelayOrDuration($event,node,_index)" 
                                    :style="computedStyle(com['animation']['group'][0].ani,node,_index)">
                                    <span class="timeline-layer-delay" v-show="node.delay * 1000 >= 150">{{ node.delay * 1000 }}</span>
                                    <span class="timeline-layer-duration">{{ node.duration * 1000 }}</span>
                                    <div class="timeline-layer-resize-handle-delay"></div>
                                    <div class="timeline-layer-resize-handle-duration"></div>
                                </div>
                            </div>
                        </div>
                        <!-- 时间轴游标 -->
                        <div class="timeline-drag-handle" :style="{ left:spotLeft +'px'}" @mousedown="changeSpot">
                            <div class="timline-drag-spot"></div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>
<script src="../js/_timeline.js"></script>
<style lang="stylus">
    @import '../css/timeline.styl'
</style>

_timeline.js:


export default {
    name: "timeline",
    props: ['timeLineData', 'componentIndex'],
    data() {
        return {
            left: 0,//模拟滚动条左偏移量
            viewLeft: 0,//刻度线左偏移量
            componentArr: [],//组件数组
            spotLeft: -10,//拖拽左偏移量
            curComponentIndex: this.componentIndex,//当前组件
            closeStyle: {},
            isCloseTimeLine: false,
            iconClassName:"el-icon-remove-outline"
        }
    },
    computed: {
        // 模拟的滚动条的左偏移量
        moveLeft() {
            return {
                left: this.left + 'px'
            }
        },
        //刻度线数,也许是一个数组
        spotArr() {
            return Math.ceil(this.maxLeftOrMaxTime('time')() / 1000) + 1 || 11;
        },
        maxScrollLeft() {
            // 990为滚动轨道的宽度,4px减少偏差
            return this.$refs.timeLineScroll.offsetWidth - 994;
        },
        viewMaxLeft() {
            // 一页显示6个刻度线,195为每个刻度线宽度,剩余刚好就是this.spotArr - 6个,所以滚动最大为195 * (this.spotArr - 6)
            if (this.spotArr > 6) {
                return 195 * (this.spotArr - 6);
            } else {
                return 0;
            }
        }
    },
    mounted() {
        // 时间轴数据
        if (this.timeLineData) {
            this.componentArr = this.timeLineData;
        }
    },
    methods: {
        closeTimeLine() {
            this.isCloseTimeLine = !this.isCloseTimeLine;
            if (this.isCloseTimeLine) {
                this.$set(this.closeStyle, 'width', 0);
                this.$set(this.closeStyle, 'height', 0);
                this.$set(this.closeStyle, 'padding', 0);
                this.iconClassName = 'el-icon-full-screen';
            } else {
                this.closeStyle = {};
                this.iconClassName = 'el-icon-remove-outline';
            }

        },
        // 计算宽度与左偏移量
        computedStyle(nodeArr, node, index) {
            if (index <= 0) {
                return { width: 10 * (node.duration * 1000 / 50) + 'px', left: 10 * node.delay * 1000 / 50 + 21 + 'px' }
            } else {
                //初始左偏移量
                let left = 21;
                nodeArr.forEach((n, nIndex) => {
                    if (nIndex <= index) {
                        left += 10 * (n.delay * 1000 / 50)
                    }
                    if (nIndex <= index - 1) {
                        left += 10 * (n.duration * 1000 / 50);
                    }
                })
                return { width: 10 * (node.duration * 1000 / 50) + 'px', left: left + 'px' }
            }
        },
        // 改变左偏移量
        changeLeft() {
            document.onmousemove = (e) => {
                this.left = e.pageX > this.maxScrollLeft ? this.maxScrollLeft : e.pageX <= 0 ? 0 : e.pageX;
                this.viewLeft = e.pageX > this.viewMaxLeft ? this.viewMaxLeft : e.pageX <= 0 ? 0 : e.pageX;
            }
            this.cancelEvent();
        },
        // 改变延迟或者执行时间
        changeDelayOrDuration(event, node, index) {
            // 判断拖拽方向
            let direction = event.clientX;
            // 块的总宽
            let total = 10 * (node.duration * 1000 / 50);
            // 如果拖拽的是执行时间轴,则改变执行时间,否则改变延迟时间
            let isDuration = event.target.className.indexOf('duration') > - 1 ? true : false;
            document.onmousemove = (e) => {
                if (e.clientX >= direction) {
                    if (isDuration) {
                        node.duration = (node.duration * 1000 + 50) / 1000;
                    } else {
                        node.delay = (node.delay * 1000 + 50) / 1000;
                    }
                } else {
                    if (isDuration) {
                        node.duration = (node.duration * 1000 - 50) / 1000;
                    } else {
                        node.delay = (node.delay * 1000 - 50) / 1000;;
                    }
                    if (node.delay <= 0) node.delay = 0;
                    if (node.duration <= 0) node.duration = 0;
                }
            }
            this.cancelEvent();
        },
        // 拖拽游标的最大值
        maxLeftOrMaxTime(type) {
            let nodeArr = [];
            this.componentArr.forEach((com) => {
                if (com['animation']['group'][0]['ani'].length) {
                    com['animation']['group'][0]['ani'].map((val) => {
                        if (type.indexOf('left') > -1) {
                            nodeArr.push(10 * (val.duration * 1000 / 50) + 10 * (val.delay * 1000 / 50));
                        } else if (type.indexOf('time') > -1) {
                            nodeArr.push(val.duration * 1000 + val.delay * 1000);
                        }
                    })
                }
            })
            return nodeArr.length > 0 ? () => {
                return Math.max(...nodeArr) || Math.max.apply(null, nodeArr);
            } : () => { return 0 };
        },
        // 时间轴游标拖动
        changeSpot() {
            this.spotLeft = -10;
            this.left = 0;
            this.viewLeft = 0;
            document.onmousemove = (e) => {
                this.spotLeft = e.pageX <= 0 ? -10 : e.pageX >= this.maxLeftOrMaxTime('left')() ? this.maxLeftOrMaxTime('left')() : e.pageX;
                if (e.pageX > this.maxScrollLeft) this.left = e.pageX - this.maxScrollLeft >= this.maxScrollLeft ? this.maxScrollLeft : e.pageX - this.maxScrollLeft;
                if (e.pageX > this.viewMaxLeft) this.viewLeft = e.pageX - this.viewMaxLeft >= this.viewMaxLeft ? this.viewMaxLeft : e.pageX - this.viewMaxLeft;
                if (e.pageX <= 0) {
                    this.left = this.viewLeft = 0;
                }
            }
            this.cancelEvent();
        },
        // 注销鼠标拖拽结束事件
        cancelEvent() {
            document.onmouseup = (e) => {
                document.onmousemove = document.onmouseup = null;
            }
        },
        // 选中组件
        selectComponent(item, index) {
            this.curComponentIndex = index;
            this.$parent.$parent.getCoverage(item.uuid);
        },
        // 删除组件动画
        deleteComponent(item) {
            if (this.componentArr.length) {
                let idx = this.componentArr.indexOf(item);
                this.componentArr[idx].animation.group[0].ani = [];
            }
        },
        // 播放组件动画
        playComponentAnimation(item,name) {
            this.spotLeft = -10;
            let spotMaxLeft = 0;
            let time = 0;
            let start = null;
            let animationName = name ? name : item.animation.group[0].name;
            if(!name){
                this.$parent.$parent.$refs.iframe.contentWindow.EditorSupport.root.previewAnimation(animationName);
            }
            if (item.animation.group[0].ani.length) {
                item.animation.group[0].ani.forEach((ani) => {
                    spotMaxLeft += 10 * (ani.duration * 1000 / 50) + 10 * (ani.delay * 1000 / 50);
                    time += ani.duration + ani.delay;
                })
                //时间轴游标运动
                let play = (t) => {
                    if (!start) start = t;
                    let progress = t - start;
                    if (progress < time * 1000) {
                        this.spotLeft = progress / 5;
                        window.requestAnimationFrame(play);
                    } else {
                        this.spotLeft = spotMaxLeft;
                    }
                }
                if (spotMaxLeft && time) {
                    window.requestAnimationFrame(play);
                }
            }
        }
    }
}

时间轴到目前为止虽然实现了基本功能,但是并不好用,因为当时开发时间轴的时候,我对需求理解的也不是很到位。当时是参考smartslider3这个里面的时间轴的功能来写的。

遇到的问题非常的多,尤其我影响深刻的是这样一个问题:

因为一个模板的数据非常的复杂,每个模板里面有一个动画的数据。我在请求模板数据的时候,用了一个变量接受它。代码如下:

//this.Decrypt方法只是一个已经封装好了的数据解密方法
let res = JSON.parse(JSON.parse(this.Decrypt(result)));
//打印出来两者不是相等的
console.log(this.Decrypt(result),res);

第一个结果如下图所示:

第二个结果如下:

针对这个问题,我足足花了三个小时的时间来排查出问题所在,我依次打印出后台返回的数据都是没有问题的。

但是就是这么奇怪,它就是多了一个ease对象,我真的很好奇,因为我的代码里确实没出现赋值代码,后面,我定位到引擎代码,结果还真的让我找到了问题所在。如下图所示:

因为引擎代码是通过webpack打包,然后我这边通过一个iframe标签来加载这个引擎代码。代码如下:

<iframe ref="iframe" class="iframe" src="../../../static/pixijs/index.html" frameborder="0" scrolling="no" @load="dataInit"></iframe>

然后,我要创建一个引擎,就需要调用引擎代码提供的createApp方法,参数就是数据以及widthheight。代码如下:

//这里的this.addData就是前面所说的res
this.$refs.iframe.contentWindow.Main.createApp(this.addData, 888, 500);

这让我清晰的认识到了js对象的引用特性。

其它项目

当然还有一些项目,但都是比较小的项目,基本用到的技术都是jquery之类的,而这一年的时间,我花在开发和维护发布后台管理系统的时间是最多的。但其它项目让我影响比较深刻的还是二天的时间完成的一个后台管理系统——报价后台管理系统。

报价后台管理系统所涉及到的功能不多,所以我完成的也算比较快,这没什么好说的。但我要总结到的就是在这个系统当中,我逐渐的规范化了代码。我将所有接口都规范在了一个文件里面,即api.js。如下:

const host = '';
const api = {
    loginAPI:host + 'UserLogin/login',//登录接口
    registerAPI:host + 'UserLogin/addUser',//添加用户接口
    exportAPI:host + 'MakeExcel/getJson',//普通用户导出数据接口
    addSystemAPI:host + 'ShowProjectController/inserShowProject',//管理员添加系统接口
    editSystemAPI:host + 'ShowProjectController/updateShowProject',//管理员编辑系统接口
    deleteSystemAPI:host + 'ShowProjectController/deleteShowProject',//管理员删除系统接口
    findSystemAPI:host + 'ShowProjectController/selectShowProject',//管理员与用户查询系统接口
    lookWareApi: host + "EquipmentController/selectEqui", //查询软件和硬件信息接口
    addWareApi: host + "EquipmentController/insertEqui", //添加软件和硬件信息接口
    editWareApi: host + "EquipmentController/updateEqui", //修改软件和硬件信息接口
    deleteWareApi: host + "EquipmentController/deleteEqui", //删除软件和硬件信息接口
    modelApi: host + "ChildController/selectChild", //查软件与硬件型号
    addModelApi: host + "ChildController/insertChild", //添加软件与硬件型号
    updateModelApi: host + "ChildController/editChild", //修改软件与硬件型号
    deleteModelApi: host + "ChildController/deleteChild", //删除软件与硬件型号
}
export default api; 

慢慢的规范化了自己的代码,这才是我最想说的,这也是这个系统带给我的收获。

发布系统与小程序的使用文档

今年2月份到3月份我的主要工作也是完成发布系统的使用文档以及小程序的使用文档,采用gitbook搭建的。也遇到了一些坑,都总结成了文章。详见

工作之余编写个人网站的文档

工作之余闲下来之后,我就写自己的个人网站里的文档,不停的学习,目前已完成了HTMLpixi.js的总结,CSS也快要完成了,希望今年能把javascript以及vue.js完成。

如有兴趣可前往我的个人网站查看。

最后

学如逆水行舟,不进则退。从2017年毕业到现在已经二年多了,其实我对自己的未来也还是有些迷茫的,因为这些都不是我想要的,我特别想做一个自由职业者,能够做出一个别样的开源项目来,那才是我的目标,所以我用闲余时间做了一个ew-color-picker的开源项目,但是,这只是一个简单的开始,也是我对开源项目的初步涉猎,我相信我还有很长的路要走,给自己一句鼓励:加油,努力每天学习一点点,每天就进步一点点。


夕水
5.2k 声望5.7k 粉丝

问之以是非而观其志,穷之以辞辩而观其变,资之以计谋而观其识,告知以祸难而观其勇,醉之以酒而观其性,临之以利而观其廉,期之以事而观其信。