NF5501扫描与打印二次开发记要

2020-02-03

关键字:热敏打印设备二次开发



NF5501 是一款便携式的打印扫描一体式设备。

这篇博文记录一下笔者针对这款设备的条码扫描与热敏打印功能的二次开发。

首先这款设备官方是有给出扫码与热敏打印的示例程序及源码的,它的下载地址为:

http://www.nanfang001.com/Support/473918/

不过官方提供的示例源码稍稍有些零乱,需要花点时间才能明白控制扫描与打印的流程。笔者这边已经大致梳理过一次了,将关键步骤在此记录下来,以期能帮助到有需要的同学。

NF5501 应用层与扫描打印硬件层通信都是通过串口节点来实现的,并且扫描与打印使用的是同一个串口节点。设备开放了 JNI 接口供 APP 调用。

集成 NF5501 的扫描打印功能需要用到以下关键文件:

1、libzyapi_common.so

2、CommonApi.java

3、beep.ogg

这几个文件在官方示例源码中都有,当然笔者也将它们打包了一份在博文里公开:

链接:https://pan.baidu.com/s/17oGkZFcwqbcOHQYTOF9o8w    提取码:608b

其中,第 1 个库文件是 JNI 实现程序,这个库文件必须正确预置进我们的 APK 中,否则在调用相关 JNI 接口时会报找不到实现函数的错误。以 Android Studio 工程为例,将这 libzyapi_common.so 预置在 ./app/src/main/jniLibs/armeabi/libzyapi_common.so 中即可。

第 2 个文件则是封装的 JNI 接口,也是必须预置。当然你也可以只保留其中几个你会用到的接口方法,把它们直接嵌入到你自己的代码中也是可以的。只要保证方法名与加载的库名称正确即可。对了,似乎这个设备的 JNI 是静态封装的,它要求我们在封装 JNI native 方法时必须以包名 android.zyapi 来命名。即如果你直接预置示例源码中的 CommonApi.java 源码,那你必须将它以 ./android/zyapi/CommonApi.java 的路径来放置。

第 3 个则是扫描成功时设备发出的提示音音频文件。这个非刚性需求,并且你完全可以根据自己的实际需求预置其它音频文件。直接将这个文件预置在 ./res/raw/beep.ogg 下即可。

文件预置好以后即可开始撸代码了。

首先我们要知道设备对应的扫描引脚号。这个引脚具体的作用是什么笔者不太清楚,只知道它对于功能初始化很重要。

这个引脚号是根据设备的型号来确定的。即 Android 代码中的 Build.MODEL 的值,如果该值是 5501H 则引脚号就是 64,如果 Build.MODEL 的值是 5501L 则引脚号就是 84。Build.MODEL 的值也可以在 adb shell 中直接通过查看系统属性来获得,对应命令为:

getprop | grep model

笔者手里的设备是 5501L 型号的,因此对应的引脚号就是 84。

private static final int PIN = 84;

接下来可以再创建多几个关键全局变量:

private final int comFd;
private CommonApi commonApi;
private MediaPlayer beeper;

然后就可以开始初始化了。关于初始化的时机,同学可以根据实际需要来决定。笔者这边是在应用进入到主界面以后才做初始化:

        commonApi = new CommonApi();
        commonApi.setGpioDir(PIN, 0);
        commonApi.getGpioIn(PIN);
        new CountDownTimer(1000, 1000){

            @Override
            public void onTick(long millisUntilFinished) {
                // do nothing.
            }

            @Override
            public void onFinish() {
                Logger.i(TAG, "1s count down onFinish()");
                commonApi.setGpioDir(PIN, 1);
                commonApi.setGpioOut(PIN, 1);
            }
        }.start();
        comFd = commonApi.openCom("/dev/ttyMT1", 115200, 8, ‘N‘, 1);
        Logger.i(TAG, "scanner serial port fd:" + comFd);

        new CountDownTimer(2000, 1000){

            @Override
            public void onTick(long millisUntilFinished) {
                // do nothing.
            }

            @Override
            public void onFinish() {
                Logger.i(TAG, "2s scanner serial port onFinish()");
                if (comFd > 0) {
                    canRead = true;
                    write(new byte[] {0x1B, 0x23, 0x23, 0x35, 0x36, 0x55, 0x50});
                    readCom.start();
                    // 默认开启黑标
                    write(new byte[] {0x1F, 0x1B, 0x1F, (byte) 0x80, 0x04, 0x05, 0x06, 0x66});
                }else{
                    canRead = false;
                }

                notifyScannerReady();
            } // onFinish() -- end
        }.start();

上面这个初始化过程其实没有什么好说的,照着写就是了。这块代码正确执行完成以后就可以发现设备右上方的绿色小指标灯已经亮起来了。

其次是创建一个子线程专门用于读取扫描打印串口的数据:

    private Thread readCom = new Thread(){

        private static final int MAX_RECV_BUF_SIZE = 1023;

        private int ret;

        private String code;

        @Override
        public void run() {
            byte[] buf = new byte[MAX_RECV_BUF_SIZE + 1];
            while (canRead) {
                ret = commonApi.readComEx(comFd, buf, MAX_RECV_BUF_SIZE, 0, 0);

                if (ret <= 0) {
                    Logger.w(TAG, "read failed!!!! ret:" + ret);
                    if(callback != null) {
                        callback.onScanResult(null);
                    }
                    try {
                        sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    continue;
                }

                byte[] recv = new byte[ret];
                System.arraycopy(buf, 0, recv, 0, ret);

                code = new String(recv, 0, ret).trim();
                Logger.i(TAG, "code scan:" + code);

                if (!code.contains("") && !code.contains("##55") && !code.contains("##56") && !code.equals("start") && code.length() > 0) {
                    beeper.start();
                }else{
                    code = null;
                }

                if(callback != null) {
                    callback.onScanResult(code);
                }
            }// while -- end
        }// run() -- end

上面代码在中下方有一个字符显示不出来 !code.contains("") 这里,那个字符在 ASCII 表中是第 19 号:

以上就是 NF5501 扫描打印功能的初始化过程。这个过程由于有延时的存在,因此需要 1 秒钟左右。当然,你也可以在初始化过程中创建好扫描成功音频播放器实例:

beeper = MediaPlayer.create(activity.getApplicationContext(), R.raw.beep);

当我们需要扫描条形码与二维码时,则可以调用以下代码:

        write(new byte[] {0x1B, 0x23, 0x23, 0x35, 0x35, 0x44, 0x4E});
        commonApi.setGpioDir(74, 1);
        commonApi.setGpioOut(74, 0);
        commonApi.setGpioDir(75, 1);
        commonApi.setGpioOut(75, 0);
        new CountDownTimer(50, 50){

            @Override
            public void onTick(long millisUntilFinished) {
                // do nothing.
            }

            @Override
            public void onFinish() {
                commonApi.setGpioDir(74, 1);
                commonApi.setGpioOut(74, 1);
                commonApi.setGpioDir(75, 1);
                commonApi.setGpioOut(75, 1);
            }
        }.start();

当我们需要打印时,则直接将要打印的数据以字节数组的形式往串口写即可:

        if (comFd > 0) {
            commonApi.writeCom(comFd, data, data.length);
        }

当然,其实在写打印数据之前需要先写相应的指令数据以让设备做好打印准备。

这些打印指令都有哪些呢?

其实打印可以归结两种:

1、文字打印;

2、图像打印。

打印文字的话需要先发送两条指令:

1、设置对齐方式;

2、设置字号。

然后才是发送文字的字节数组数据。这里需要提一点,这个设备打印文字时是“按行打印”的,简单说就是你必须在你要打印的文字的末尾至少加一个换行符。下面是官方源码中打印文字的代码:

    public static void printText(int size, int align, String text) {

        switch (align) {
            case 0:
                send(new byte[] { 0x1b, 0x61, 0x00 });
                break;
            case 1:
                send(new byte[] { 0x1b, 0x61, 0x01 });
                break;
            case 2:
                send(new byte[] { 0x1b, 0x61, 0x02 });
                break;

            default:
                break;
        }
        switch (size) {
            case 1:
                send(new byte[] { 0x1D, 0x21, 0x00 });
                break;
            case 2:
                send(new byte[] { 0x1D, 0x12, 0x11 });
                break;

            default:
                break;
        }
        // 打印
        try {
            send((text + "\n").getBytes("GBK"));
            send(new byte[] { 0x1D, 0x0c });
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

而打印图片,则是需要我们先准备好 Bitmap 格式的图片,再将它转成字节数组形式。将Bitmap 图片转成字节数组需要用到专用方法,具体请参阅下列代码,也是取自官方示例源码:

public void print(PrintInfo pi) {
        Bitmap bitmap = makePrintImage(pi);
        if(bitmap != null) {
            write(new byte[] {0x1b, 0x61, 0x00});
            byte[] bm = draw2PxPoint(bitmap);
            write(bm);
        }else{
            Logger.e(TAG, "Cannot print cause ‘bitmap‘ is null.");
        }
    }

    private byte[] draw2PxPoint(Bitmap bmp) {
        int size = bmp.getWidth() * bmp.getHeight() / 8 + 1200;
        Logger.i(TAG, "size1:" + size + ",bitmap size:" + bmp.getByteCount());
        byte[] data = new byte[size];
        int k = 0;
        // 设置行距为0的指令
        data[k++] = 0x1B;
        data[k++] = 0x33;
        data[k++] = 0x00;
        // 逐行打印
        for (int j = 0; j < bmp.getHeight() / 24f; j++) {
            // 打印图片的指令
            data[k++] = 0x1B;
            data[k++] = 0x2A;
            data[k++] = 33;
            data[k++] = (byte) (bmp.getWidth() % 256); // nL
            data[k++] = (byte) (bmp.getWidth() / 256); // nH
            // 对于每一行,逐列打印
            for (int i = 0; i < bmp.getWidth(); i++) {
                // 每一列24个像素点,分为3个字节存储
                for (int m = 0; m < 3; m++) {
                    // 每个字节表示8个像素点,0表示白色,1表示黑色
                    for (int n = 0; n < 8; n++) {
                        byte b = px2Byte(i, j * 24 + m * 8 + n, bmp);
                        if (k < size) {
                            data[k] += data[k] + b;
                        }
                        // data[k] = (byte) (data[k]+ data[k] + b);
                    }
                    k++;
                }
            }
            if (k < size) {
                data[k++] = 10;// 换行
            }
        }
        return data;
    }

    private byte px2Byte(int x, int y, Bitmap bit) {
        if (x < bit.getWidth() && y < bit.getHeight()) {
            byte b;
            int pixel = bit.getPixel(x, y);
            int red = (pixel & 0x00ff0000) >> 16; // 取高两位
            int green = (pixel & 0x0000ff00) >> 8; // 取中两位
            int blue = pixel & 0x000000ff; // 取低两位
            int gray = RGB2Gray(red, green, blue);
            if (gray < 128) {
                b = 1;
            } else {
                b = 0;
            }
            return b;
        }
        return 0;
    }

    private int RGB2Gray(int r, int g, int b) {
        return (int) (0.29900 * r + 0.58700 * g + 0.11400 * b); // 灰度转化公式
    }

在使用完毕后,应用退出时最好将扫描打印功能去初始化操作一下。代码比较简单,如下:

    @Override
    public void destroy(Activity activity) {
        if (comFd > 0) {
            commonApi.setGpioMode(84, 0);
            commonApi.setGpioDir(84, 0);
            commonApi.setGpioOut(84, 0);
            commonApi.closeCom(comFd);
        }
    }

上面的代码在正确执行完以后可以发现设备右上方的绿色指示灯会熄灭。

以下贴出笔者使用的完整扫描打印功能封装代码:

package com.jarwen.scanner.scanner;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.media.MediaPlayer;
import android.os.CountDownTimer;
import android.zyapi.CommonApi;

import com.jarwen.scanner.MainActivity;
import com.jarwen.scanner.R;
import com.jarwen.scanner.data.model.PrintInfo;
import com.jarwen.scanner.util.Logger;

import com.jarwen.scanner.util.ToastManager;

public class NF5501 extends Scanner implements MainActivity.OnScanKeyListener {

    private static final String TAG = "NF5501";

    private static final int PIN = 84; // no why.
    private final int comFd;

    private boolean canRead;

    private OnScanResultCallback callback;
    private CommonApi commonApi;
    private MediaPlayer beeper;

    NF5501(Context context, ToastManager tm){
        super(context, tm);
        Logger.i(TAG, "new NF5501()");

        commonApi = new CommonApi();
        commonApi.setGpioDir(PIN, 0);
        commonApi.getGpioIn(PIN);
        new CountDownTimer(1000, 1000){

            @Override
            public void onTick(long millisUntilFinished) {
                // do nothing.
            }

            @Override
            public void onFinish() {
                Logger.i(TAG, "1s count down onFinish()");
                commonApi.setGpioDir(PIN, 1);
                commonApi.setGpioOut(PIN, 1);
            }
        }.start();
        comFd = commonApi.openCom("/dev/ttyMT1", 115200, 8, ‘N‘, 1);
        Logger.i(TAG, "scanner serial port fd:" + comFd);

        new CountDownTimer(2000, 1000){

            @Override
            public void onTick(long millisUntilFinished) {
                // do nothing.
            }

            @Override
            public void onFinish() {
                Logger.i(TAG, "2s scanner serial port onFinish()");
                if (comFd > 0) {
                    canRead = true;
                    write(new byte[] {0x1B, 0x23, 0x23, 0x35, 0x36, 0x55, 0x50});
                    readCom.start();
                    // 默认开启黑标
                    write(new byte[] {0x1F, 0x1B, 0x1F, (byte) 0x80, 0x04, 0x05, 0x06, 0x66});
                }else{
                    canRead = false;
                }

                notifyScannerReady();
            } // onFinish() -- end
        }.start();
    }// NF5501() -- end

    @Override
    public void prepare(Activity activity) {
        beeper = MediaPlayer.create(activity.getApplicationContext(), R.raw.beep);
        ((MainActivity)activity).setOnScanKeyListener(this);
    }

    @Override
    public void resume(OnScanResultCallback callback) {
        Logger.v(TAG, "resume()");
        this.callback = callback;
    }

    @Override
    public void pause() {
        Logger.v(TAG, "pause()");
        callback = null;
    }

    @Override
    public void destroy(Activity activity) {
        Logger.v(TAG, "destroy(), comFd:" + comFd);
        canRead = false;
        if (comFd > 0) {
            commonApi.setGpioMode(84, 0);
            commonApi.setGpioDir(84, 0);
            commonApi.setGpioOut(84, 0);
            commonApi.closeCom(comFd);
        }
    }

    @Override
    public void scan() {
        Logger.v(TAG, "scan()");

        write(new byte[] {0x1B, 0x23, 0x23, 0x35, 0x35, 0x44, 0x4E});
        commonApi.setGpioDir(74, 1);
        commonApi.setGpioOut(74, 0);
        commonApi.setGpioDir(75, 1);
        commonApi.setGpioOut(75, 0);
        new CountDownTimer(50, 50){

            @Override
            public void onTick(long millisUntilFinished) {
                // do nothing.
            }

            @Override
            public void onFinish() {
                commonApi.setGpioDir(74, 1);
                commonApi.setGpioOut(74, 1);
                commonApi.setGpioDir(75, 1);
                commonApi.setGpioOut(75, 1);
            }
        }.start();
    }

    private void write(byte[] data){
        if (comFd > 0) {
            commonApi.writeCom(comFd, data, data.length);
        }
    }

    @Override
    public void onScanKeyClicked() {
        Logger.v(TAG, "onScanKeyClicked(),canRead:" + canRead + ",callback null?" + (callback == null));
        if(canRead && callback != null) {
            scan();
        }
    }

    @Override
    public void print(PrintInfo pi) {
        Logger.v(TAG, "print(),order number:" + pi.getOrderNo());

        Bitmap bitmap = makePrintImage(pi);
        if(bitmap != null) {
            write(new byte[] {0x1b, 0x61, 0x00});
            byte[] bm = draw2PxPoint(bitmap);
            write(bm);
        }else{
            Logger.e(TAG, "Cannot print cause ‘bitmap‘ is null.");
        }

        Logger.i(TAG, "print end.");
    } // print() -- end.

    /*************************************************************************
     * 假设一个240*240的图片,分辨率设为24, 共分10行打印 每一行,是一个 240*24 的点阵, 每一列有24个点,存储在3个byte里面。
     * 每个byte存储8个像素点信息。因为只有黑白两色,所以对应为1的位是黑色,对应为0的位是白色。
     * ------
     * 把一张Bitmap图片转化为打印机可以打印的字节流
     */
    private byte[] draw2PxPoint(Bitmap bmp) {
        // 用来存储转换后的 bitmap 数据。为什么要再加1000,这是为了应对当图片高度无法
        // 整除24时的情况。比如bitmap 分辨率为 240 * 250,占用 7500 byte,
        // 但是实际上要存储11行数据,每一行需要 24 * 240 / 8 =720byte 的空间。再加上一些指令存储的开销,
        // 所以多申请 1000byte 的空间是稳妥的,不然运行时会抛出数组访问越界的异常。
        int size = bmp.getWidth() * bmp.getHeight() / 8 + 1200;
        Logger.i(TAG, "size1:" + size + ",bitmap size:" + bmp.getByteCount());
        byte[] data = new byte[size];
        int k = 0;
        // 设置行距为0的指令
        data[k++] = 0x1B;
        data[k++] = 0x33;
        data[k++] = 0x00;
        // 逐行打印
        for (int j = 0; j < bmp.getHeight() / 24f; j++) {
            // 打印图片的指令
            data[k++] = 0x1B;
            data[k++] = 0x2A;
            data[k++] = 33;
            data[k++] = (byte) (bmp.getWidth() % 256); // nL
            data[k++] = (byte) (bmp.getWidth() / 256); // nH
            // 对于每一行,逐列打印
            for (int i = 0; i < bmp.getWidth(); i++) {
                // 每一列24个像素点,分为3个字节存储
                for (int m = 0; m < 3; m++) {
                    // 每个字节表示8个像素点,0表示白色,1表示黑色
                    for (int n = 0; n < 8; n++) {
                        byte b = px2Byte(i, j * 24 + m * 8 + n, bmp);
                        if (k < size) {
                            data[k] += data[k] + b;
                        }
                        // data[k] = (byte) (data[k]+ data[k] + b);
                    }
                    k++;
                }
            }
            if (k < size) {
                data[k++] = 10;// 换行
            }
        }
        return data;
    }

    /**
     * 灰度图片黑白化,黑色是1,白色是0
     *
     * @param x
     *            横坐标
     * @param y
     *            纵坐标
     * @param bit
     *            位图
     */
    private byte px2Byte(int x, int y, Bitmap bit) {
        if (x < bit.getWidth() && y < bit.getHeight()) {
            byte b;
            int pixel = bit.getPixel(x, y);
            int red = (pixel & 0x00ff0000) >> 16; // 取高两位
            int green = (pixel & 0x0000ff00) >> 8; // 取中两位
            int blue = pixel & 0x000000ff; // 取低两位
            int gray = RGB2Gray(red, green, blue);
            if (gray < 128) {
                b = 1;
            } else {
                b = 0;
            }
            return b;
        }
        return 0;
    }

    /**
     * 图片灰度的转化
     */
    private int RGB2Gray(int r, int g, int b) {
        return (int) (0.29900 * r + 0.58700 * g + 0.11400 * b); // 灰度转化公式
    }

    private Thread readCom = new Thread(){

        private static final int MAX_RECV_BUF_SIZE = 1023;

        private int ret;

        private String code;

        @Override
        public void run() {
            byte[] buf = new byte[MAX_RECV_BUF_SIZE + 1];
            while (canRead) {
                ret = commonApi.readComEx(comFd, buf, MAX_RECV_BUF_SIZE, 0, 0);

                if (ret <= 0) {
                    Logger.w(TAG, "read failed!!!! ret:" + ret);
                    if(callback != null) {
                        callback.onScanResult(null);
                    }
                    try {
                        sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    continue;
                }

                byte[] recv = new byte[ret];
                System.arraycopy(buf, 0, recv, 0, ret);

                code = new String(recv, 0, ret).trim();
                Logger.i(TAG, "code scan:" + code);

                if (!code.contains("") && !code.contains("##55") && !code.contains("##56") && !code.equals("start") && code.length() > 0) {
                    beeper.start();
                }else{
                    code = null;
                }

                if(callback != null) {
                    callback.onScanResult(code);
                }

//                StringBuilder sb = new StringBuilder();
//                for (int i = 0; i < recv.length; i++) {
//                    if (recv[i] == 0x0D) {
//                        sb.append("\n");
//                    } else {
//                        sb.append((char) recv[i]);
//                    }
//                }
//                Logger.d(TAG, "sb:" + sb.toString());
                String str = byteToString(buf, ret);
                Logger.i(TAG, "hex data:" + str);
                if (str.contains("1C 00 0C 0F")) {
//                    Logger.i(TAG, "no paper");
//                    Intent mIntent = new Intent("NOPAPER");
//                    instance.sendBroadcast(mIntent);
//                    isCanprint = false;
                    return;
                } else {
//                    Logger.i(TAG, "can print");
                }
            }// while -- end
        }// run() -- end

        private String byteToString(byte[] b, int size) {
            byte high, low;
            byte maskHigh = (byte) 0xf0;
            byte maskLow = 0x0f;

            StringBuilder buf = new StringBuilder();

            for (int i = 0; i < size; i++) {
                high = (byte) ((b[i] & maskHigh) >> 4);
                low = (byte) (b[i] & maskLow);
                buf.append(findHex(high));
                buf.append(findHex(low));
                buf.append(" ");
            }

            return buf.toString();
        }

        private char findHex(byte b) {
            int t = Byte.valueOf(b).intValue();
            t = t < 0 ? t + 16 : t;
            if ((0 <= t) && (t <= 9)) {
                return (char) (t + ‘0‘);
            }

            return (char) (t - 10 + ‘A‘);
        }
    };
}

NF5501扫描打印功能封装代码

最后,关于如何通过代码自定义生成打印小票的 Bitmap 图片,可以参阅笔者的另一篇博文:

https://www.cnblogs.com/chorm590/p/12240407.html



NF5501扫描与打印二次开发记要

原文地址:https://www.cnblogs.com/chorm590/p/11656177.html

时间: 2024-07-30 05:21:46

NF5501扫描与打印二次开发记要的相关文章

PDA手持终端集成一体打印 二次开发

PDA手持终端集成一体打印 二次开发支持 VS2008或VS2005开发工具 c#或C++开发语言 Mobile6.5,支持GSM通话,GPRS,EDGE网络;内置wifi,蓝牙,gps商场单品管理小票打印等仓储以及物流管理终端 POS,移动POS机,打印PDA,打印掌上电脑,小票打印,小票打印机 手持POS打印智能终端采用Mobilie6.5操作系统,GSM/GPRS电话功能,WIFI,BT,14443AB RFID读写模块,远距离红光条码扫描模组,200万摄像头,2英寸热敏打印头.为一款工业

手机端扫描识别车牌二次开发接口

支持android.ios等多种主流移动操作系统.Android平台提供Jar包,IOS平台提供.a静态库,该产品期初仅支持拍照识别车牌,如今技术日新月异,用手机扫描识别车牌已经不是什么难事儿.从原本的采用手机.平板电脑摄像头拍摄汽车牌照图像,转变为通过手机扫描识别车牌,此OCR软件对车牌颜色.车牌号进行识别. 技术支持:15011261084(微信同)也可百度“车牌识别王晨”了解更多 手机扫描识别车牌号的功能描述 手机扫描识别车牌号的功能SDK是我公司开发的基于移动平台的车牌识别软件开发包,支

使用Java开发多线程端口扫描工具(二)

一 介绍 这一篇文章是紧接着上一篇文章(http://www.zifangsky.cn/2015/12/使用java开发多线程端口扫描工具/)写的,端口扫描的原理不用多少,我在上一篇文章中已经说过了,至于目的大家都懂得.在这一篇文章里,我主要是对端口扫描工具的继续完善,以及写出一个比较直观的图形界面出来,以方便我们测试使用.界面如下: 这个工具主要是实现了以下几点功能:(1)两种扫描方式,一种是只扫描常见端口,另一种是设置一个起始和结束端口,依次探测.当然,原理很简单,用for循环就可以了:(2

微信服务器与项目服务器的交互(关注功能、微信扫描带参数二维码)

<?php /** * wechat php test */ //define your token define("TOKEN", "txtj"); $wechatObj = new wechatCallbackapiTest(); if (isset($_GET['echostr'])) { $wechatObj->valid(); }else{ $wechatObj->responseMsg(); } class wechatCallback

java快速开发平台 二次开发 SSM后台框架

获取[下载地址]     [免费支持更新]三大数据库 mysql  oracle  sqlsever   更专业.更强悍.适合不同用户群体[新录针对本系统的视频教程,手把手教开发一个模块,快速掌握本系统] A 集成代码生成器 [正反双向(单表.主表.明细表.树形表,开发利器)+快速构建表单; freemaker模版技术 ,0个代码不用写,生成完整的一个模块,带页面.建表sql脚本,处理类,service等完整模块B 集成阿里巴巴数据库连接池druid;  数据库连接池  阿里巴巴的 druid.

AutoCAD二次开发&mdash;&mdash;AutoCAD.NET API开发环境搭建

AutoCAD二次开发--AutoCAD.NET API开发环境搭建 AutoCAD二次开发--AutoCAD.NET API开发环境搭建 AutoCAD二次开发工具:1986年AutoLisp,1989年ADS,1990年DCL,1993年ADS-RX,1995年ObjectARX,1996年Active X Automation(COM),1997年VBA,1998年Visual Lisp,2006年.net API(DLL). 趋势和方向:AutoCAD.net API(AutoCAD20

java快速开发平台 二次开发 外包项目利器 springmvc SSM后台框架源码

获取[下载地址]   [免费支持更新]三大数据库 mysql  oracle  sqlsever   更专业.更强悍.适合不同用户群体[新录针对本系统的视频教程,手把手教开发一个模块,快速掌握本系统] A集成代码生成器 [正反双向(单表.主表.明细表.树形表,开发利器)+快速构建表单;freemaker模版技术 ,0个代码不用写,生成完整的一个模块,带页面.建表sql脚本,处理类,service等完整模块B 集成阿里巴巴数据库连接池druid;  数据库连接池  阿里巴巴的 druid.Drui

【流媒体开发】VLC Media Player - Android 平台源码编译 与 二次开发详解 (提供详细800M下载好的编译源码及eclipse可调试播放器源码下载)

作者 : 韩曙亮  博客地址 : http://blog.csdn.net/shulianghan/article/details/42707293 转载请注明出处 : http://blog.csdn.net/shulianghan VLC 二次开发 视频教程 : http://edu.csdn.net/course/detail/355 博客总结 : -- 本博客目的 : 让 Android 开发者通过看本博客能够掌握独立移植 VLC Media Player 核心框架到自己的 app 中,

Ecshop系统二次开发教程及流程演示

来源:互联网 作者:佚名 时间:03-01 16:05:31 [大 中 小] Ecshop想必大家不会觉得陌生吧,大部分的B2C独立网店系统都用的是Ecshop系统,很受用户的喜爱,但是由于Ecshop模板自带有很多Ecshop的Logo和版权信息,和一些其他需要修改的地方,所以我们需要对Ecshop系统做二次开发,下面就来看看具体操作步骤吧 一.Ecshop简介: ECShop是Comsenz公司推出的一款B2C独立网店系统,适合企业及个人快速构建个性化网上商店.系统是基于PHP语言及MYSQ