那些年使用过的JFinal

说明 : 关于微信支付与隐藏或显示右上角按钮,都需要引用微信的JS文档

因为本人的项目业务功能有限,所以没有涉及大量的代码操作,如果想要对于JFinal有更深入的了解,可以参看官方文档或在JFinal-weixin的文档。

需求

利用微信公众号进行开发一个公众号平台,包括微信支付、微信认证等功能, 同时包括一个后台管理。

使用的原因

因为本人从业时间较短,不太清楚微信内部如何使用,所以就从码云上找了关于微信公众号或小程序的开发体系。结果找到了JFinal-weixin,发现这个框架比较好用, 所以这里采用了这个框架进行搭建项目 。

开发工具

  • IDEA
  • MySQL
  • Tomcat
  • Maven

主要使用IDEA 作为 IDE 进行开发, 后台数据库使用MySQL ,部署在 Tomcat 上进行运行, 使用Maven作为项目构建体系。

项目构建

依赖信息

使用Maven 构建项目, 导入Maven依赖。依赖如下:

<dependencies>
	<!-- 单元测试 -->
     <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
	<!-- Jfinal-weixin依赖 -->
    <dependency>
      <groupId>com.jfinal</groupId>
      <artifactId>jfinal-weixin</artifactId>
      <version>2.3</version>
    </dependency>
	<!-- MySQL连接数据库 -->
    <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.15</version>
    </dependency>
	<!-- 使用Druid 连接数据库 ,主要是连接池 -->
    <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.16</version>
    </dependency>
	<!-- 使用JSP作为页面展示效果 -->
    <!-- https://mvnrepository.com/artifact/javax.servlet.jsp/javax.servlet.jsp-api -->
    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>javax.servlet.jsp-api</artifactId>
      <version>2.3.3</version>
      <scope>provided</scope>
    </dependency>
	<!-- 使用JSTL依赖 -->
    <!-- https://mvnrepository.com/artifact/javax.servlet/jstl -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
    </dependency>
	<!-- 尽管在JFinal-weixin中使用了JFinal,但是如果不引用此依赖的话,会有部分类没有支持 -->
    <!-- https://mvnrepository.com/artifact/com.jfinal/jfinal -->
    <dependency>
      <groupId>com.jfinal</groupId>
      <artifactId>jfinal</artifactId>
      <version>4.0</version>
    </dependency>
	<!-- 因为要记录每个用户访问那个网站,用了什么参数,所以这里加入了Servlet的依赖 -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>4.0.1</version>
      <scope>provided</scope>
    </dependency>

  </dependencies>
创建WeixinConfig文件

WeixinConfig 是针对于整个项目的配置,包括使用什么模版信息,配置路由(JFinal中叫路由信息,其实本质是Servlet中的映射)等信息。具体如下:

import cn.shocksoft.model._MappingKit;
import com.jfinal.config.*;
import com.jfinal.json.FastJsonFactory;
import com.jfinal.kit.PropKit;
import com.jfinal.plugin.activerecord.ActiveRecordPlugin;
import com.jfinal.plugin.druid.DruidPlugin;
import com.jfinal.render.ViewType;
import com.jfinal.template.Engine;
import com.jfinal.weixin.sdk.api.ApiConfigKit;
// 这里根据官方的描述,一定是要继承JFinalConfig 这个类的,否则配置信息不会生效
public class WeixinConfig extends JFinalConfig {
    /**
     * 此方法用来配置JFinal常量值,如开发模式常量devMode的配置
     * @param constants
     */
    @Override
    public void configConstant(Constants constants) {
        // 加载配置文件(这个配置文件是放在resources 下的)
        PropKit.use("a_little_config.txt");
        // 读取配置文件,判断是否为开发模式,默认 false
        constants.setDevMode(PropKit.getBoolean("devMode" ));
        // ApiConfigKit 设为开发模式可以在开发阶段输出请求交互的 xml 与 json 数据
        ApiConfigKit.setDevMode(constants.getDevMode());
        // 默认使用的jackson,下面示例是切换到fastJson
        constants.setJsonFactory( new FastJsonFactory() );
        // 默认使用的是 JFinal 内部的 Template , 这里设置成 JSP 模板 (支持Velocity、FreeMarker等模板)
        constants.setViewType(ViewType.JSP);
    }

    /**
     * 配置路由信息 : 也就是 配置对应的 Controller
     * @param routes
     */
    @Override
    public void configRoute(Routes routes) {
		// 这里配置Controller的信息,到了具体的配置的时候,详细描述。
    }

    /**
     * 配置插件信息
     * @param plugins
     */
    @Override
    public void configPlugin(Plugins plugins) {
        // 配置 druid 数据库连接池插件
        DruidPlugin druidPlugin = new DruidPlugin(PropKit.get("jdbcUrl"), PropKit.get("user"), PropKit.get("password").trim());
        plugins.add(druidPlugin);

        // 配置ActiveRecord插件
        ActiveRecordPlugin arp = new ActiveRecordPlugin(druidPlugin);
        // 所有映射在 MappingKit 中自动化搞定 ( 也就是将实体类 添加到 对应的插件中)
		// arp.addMapping("user", User.class); // 其实应该是这样的,但是封装了一个类,从而变成下面的样子
        _MappingKit.mapping( arp );
        plugins.add(arp);
    }

    @Override
    public void configEngine(Engine engine) {

    }


    /**
     * 此方法用来配置JFinal的全局拦截器,全局拦截器将拦截所有 action 请求,
     * 除非使用@Clear在Controller中清除
     * @param interceptors
     */
    @Override
    public void configInterceptor(Interceptors interceptors) {

    }

    @Override
    public void configHandler(Handlers handlers) {

    }
}

注意 : 这个配置中使用了配置文件,这个配置文件是一个txt格式的文本文件,位于resources目录下。这个配置文件的内容是键值对的形式,与properties文件的形式是一样的, 可以将任何需要配置的东西放入到这个配置文件中。

配置Web.xml文件

主要是配置过滤器 ,如下所示 :

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" 
         id="WebApp_ID" version="4.0">
  <filter>
    <filter-name>jfinal</filter-name>
    <filter-class>com.jfinal.core.JFinalFilter</filter-class>
    <init-param>
      <param-name>configClass</param-name>
      <param-value>cn.shocksoft.config.WeiXinConfig</param-value>
    </init-param>
  </filter>

  <filter-mapping>
    <filter-name>jfinal</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
</web-app>

这里使用了4.0的版本,如果想要部署在tomcat上的话,那么就需要使用tomcat9的版本支持。

a_little_config.txt 配置文件

这个配置文件与properties文件的形式一致, 具体如下所示 :

# 数据库相关设置
jdbcUrl=jdbc:mysql://localhost:3306/xxx?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&serverTimezone=GMT%2B8
user=xxx
password=xxx

# 开发模式
devMode = true

这里放入了对应的连接数据库的配置信息,那么在 configPlugin 中,使用Druid的时候就可以使用这里的信息,从而连接数据库。

如果想要在代码中使用这里的配置文件,那么可以参看以下方式进行处理:

String jdbcUrl = PropKit.get("jdbcUrl") ;

这里使用了内部的工具类PropKit,就可以进行使用了 。

根据数据库生成对应的实体类

当数据库中的表过多的时候,可以使用程序进行生成数据库表对应的实体。 操作如下 :

import com.jfinal.kit.PathKit;
import com.jfinal.kit.PropKit;
import com.jfinal.plugin.activerecord.generator.Generator;
import com.jfinal.plugin.druid.DruidPlugin;
import javax.sql.DataSource;
public class TestGenerator {
    public static void main(String[] args) {
        // base model 所使用的包名
        String baseModelPackageName = "cn.shocksoft.model.base";
        // base model 文件保存路径
        String baseModelOutputDir = PathKit.getWebRootPath() + "/src/test/cn/shocksoft/test/base";

        // model 所使用的包名 (MappingKit 默认使用的包名)
        String modelPackageName = "cn.shocksoft.model";
        // model 文件保存路径 (MappingKit 与 DataDictionary 文件默认保存路径)
        String modelOutputDir = baseModelOutputDir + "/..";

        // 创建生成器
        Generator generator = new Generator(getDataSource(), baseModelPackageName, baseModelOutputDir, modelPackageName, modelOutputDir);
        // 设置是否生成链式 setter 方法
        generator.setGenerateChainSetter(false);
        // 添加不需要生成的表名
//        generator.addExcludedTable("adv");
        // 设置是否在 Model 中生成 dao 对象
        generator.setGenerateDaoInModel(true);
        // 设置是否生成链式 setter 方法
        generator.setGenerateChainSetter(true);
        // 设置是否生成字典文件
        generator.setGenerateDataDictionary(false);
        // 设置需要被移除的表名前缀用于生成modelName。例如表名 "osc_user",移除前缀 "osc_"后生成的model名为 "User"而非 OscUser
//        generator.setRemovedTableNamePrefixes("t_");
        // 生成
        generator.generate();
    }
    public static DataSource getDataSource() {
        PropKit.use("a_little_config.txt");
        DruidPlugin druidPlugin = new DruidPlugin(PropKit.get("jdbcUrl"), PropKit.get("user"), PropKit.get("password").trim());
        druidPlugin.start();
        return druidPlugin.getDataSource();
    }
}

这里只需要修改对应的包名即可, 因为这里会生成当前数据库下的所有表,对应的实体类。而修改对应的包名表示将对应的实体类放入到指定的包。

配置Controller的映射关系

自己根据业务需求书写相应的Controller , 但是写完一个Controller 之后, 在JFinal体系中并不能够识别。因此这里需要将对应的Controller 进行 配置的管理。示例如下 :

import com.jfinal.weixin.sdk.jfinal.MsgControllerAdapter;
import com.jfinal.weixin.sdk.msg.in.InTextMsg;
import com.jfinal.weixin.sdk.msg.in.event.InFollowEvent;
import com.jfinal.weixin.sdk.msg.in.event.InMenuEvent;
/**
 * MsgController 专门负责 对 微信用户发来的消息(包括点击菜单等)进行处理。
 * 此类需要实现 MsgControllerAdapter 类,才可以针对于 用户"指令"进行各种操作
 */
public class MsgController extends MsgControllerAdapter {
    /**
     * 关注/取关事件
     * @param inFollowEvent
     */
    @Override
    protected void processInFollowEvent(InFollowEvent inFollowEvent) {
     // 可以书写对应的逻辑
    }

    /**
     * 接收文本消息事件 : 可以针对于 文本消息进行处理
     * 这里将所有的消息 都认为是 文本消息。
     * @param inTextMsg
     */
    @Override
    protected void processInTextMsg(InTextMsg inTextMsg) {
		// 可以写具体的事件分析
    }

    /**
     * 自定义菜单事件 : 主要针对于菜单事件进行处理
     * @param inMenuEvent
     */
    @Override
    protected void processInMenuEvent(InMenuEvent inMenuEvent) {
		// 事件处理
    }
}

当然, 写完Controller之后,依旧表示不可以使用,如果想要直接访问的话,其实是没有办法访问的。需要在WeixinConfig.java中进行做配置处理。如下所示 :

 @Override
    public void configRoute(Routes routes) {
        routes.add("/msg" , MsgController.class ) ; 
    }

这里仅仅加入了 对应的一行处理,那么此时就可以将MsgController进行使用了。

使用Controller

如果要使用Controller的话, 比如说写自己的业务的时候,那么就需要定义自己的Controller,比如说,这里有一个关于用户的处理,那么就可以自己定义自己的Controller, 但是关于用户的操作,通常有以下几种做法:

1、直接使用微信对应的信息即可。
2、在系统内部使用自己的一套用户体系。

但是无论使用哪种方式,都有可能需要获取到用户对应的信息,那么如果是第三方登录的话,那么就需要涉及第三方安全认证和授权(OAuth2)。具体示例如下:

  • UserController
import cn.shocksoft.utils.Constants;
import com.jfinal.core.Controller;
import com.jfinal.weixin.sdk.api.ApiResult;
import com.jfinal.weixin.sdk.api.SnsAccessToken;
import com.jfinal.weixin.sdk.api.SnsAccessTokenApi;
import com.jfinal.weixin.sdk.api.SnsApi;

/**
 * 在 JFinal 的体系中, 不存在方法重载一说。
 * 这里将每一个方法都当做一个Action
 * 那么在WeixinConfig中进行配置的路径就是在指定了这个路径。
 * 可以对比SpringMVC中的体系 : 在 SpringMVC的时候可以在Controller类上加上一个RequestMapping的映射。
 * 同时可以在 Controller对应的方法加上 RequestMapping注解 。
 * 那么在JFinal体系中: 类上的RequestMapping写在了 WeixinConfig中, 方法上的RequestMapping则默认对应其方法名。
 */
public class UserController extends Controller {
    /**
     * 获取用户信息,在这里进行了授权操作
     */
   public void getUserInfoOauth(){
        //回调地址(必须在公网进行访问)
       String backUrl = Constants.HOST +"/user/getUserinfos";
       // 进行授权操作,并指定回调地址
       String weixinUrl = SnsAccessTokenApi.getAuthorizeURL(Constants.APP_ID, backUrl, false);
       redirect(weixinUrl);
   }

    /**
     * 这里指定了回调地址,同时可以进行任务操作
     */
   public void getUserinfos(){
       String code = get("code");  // 获取回调的 code 码
       // 将code 码转换成 具体的 accessToken
       SnsAccessToken accessToken = SnsAccessTokenApi.getSnsAccessToken(Constants.APP_ID, Constants.APP_Secret, code);
       // 根据 accessToken 与 openId 获取对应的用户信息
       ApiResult apiResult = SnsApi.getUserInfo(accessToken.getAccessToken(), accessToken.getOpenid());
       // 以 JSON的方式将获取到的 信息 输出
       System.out.println( apiResult.getJson() );
       // 在这里可以进行相应的业务处理
       // 转到对应的页面
       render("/userInfos.jsp");
   }
}

当然, 写了Controller之后,也需要在WeixinConfig.java中进行配置。示例如下 :

    /**
     * 配置路由信息 : 也就是 配置对应的 Controller
     * @param routes
     */
    @Override
    public void configRoute(Routes routes) {
        routes.add("/msg" , MsgController.class ) ;
        // 可以看到,这里添加了一行,同时这里传入了三个参数。
        routes.add( "/user", UserController.class , "/pages/user") ;
    }

这里的三个参数表示的意思如下 :

  • /user 类似于SpringMVC上Controller类上的 RequestMapping注解
  • UserController.class 表示第一个参数的路径指向哪一个Controller
  • /pages/user 表示 将要返回的页面是 存放于哪个路径之下的。

如果不清楚如何配置的话,可以查看JFinal文档进行查看。

微信支付

关于微信支付,依旧是需要对接微信的支付接口。但是在JFinal-weixin中,已经有对应的demo进行处理。可以参看直接使用。示例如下 : 页面输入金额,然后点击确定按钮:

<form id="form"  action="/user/payInfo" method="post">
<input placeholder="请输入充值金额" type="number" id="money" name="money"
onkeyup="this.value=this.value.replace(/\D/g,'')" onafterpaste="this.value=this.value.replace(/\D/g,'')">
<input type="submit" value="提交" >
</form>

当提交表单之后,需要有对应的Controller对其进行处理,因为路径是/user/payInfo , 那么在这里就需要明确这个方法放在哪里。 处理支付:

 /**
     * 写自己的支付逻辑
     */
    public void payInfo() {
        String notify_url = Constants.HOST + "/user/pay_notify";
        // openId,采用 网页授权获取 access_token API:SnsAccessTokenApi获取
        String openId = getPara("openid");
        // 因为金钱的单位是分,所以这里需要*100 再进行操作
        String money = new BigDecimal(getPara("money")).multiply(new BigDecimal(100)).toString();
        // 统一下单文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
        Map<String, String> params = new HashMap<>();
        params.put("appid", Constants.APP_ID);
        params.put("mch_id", ""); // 这里需要根据自己申请的公众号的东西,从而进行处理
        params.put("body", "购买积分");
        params.put("out_trade_no", System.currentTimeMillis() + ""); // 用户商户自己的订单号
        params.put("total_fee", money);
        String ip = IpKit.getRealIp(getRequest());
        // 处理多IP的情况, 为了处理双卡双待的情况
        if (StrKit.isBlank(ip)) {
            ip = "127.0.0.1";
        }
        if (ip.contains(",")) {
            ip = ip.split(",")[0];
        }
        params.put("spbill_create_ip", ip);
        params.put("trade_type", PaymentApi.TradeType.JSAPI.name());
        params.put("nonce_str", System.currentTimeMillis() / 1000 + ""); // 随机字符串
        params.put("notify_url", notify_url);
        params.put("openid", openId);
        // 这里填写的是paternerKey , 这个与上方的mch_id 是一起申请的
        String sign = PaymentKit.createSign(params, "");
        params.put("sign", sign);
        String xmlResult = PaymentApi.pushOrder(params);
        Map<String, String> result = PaymentKit.xmlToMap(xmlResult);
        String return_code = result.get("return_code");
        String return_msg = result.get("return_msg");
        if (!StrKit.isBlank(return_code) && "SUCCESS".equals(return_code)) {
            String result_code = (String) result.get("result_code");
            if (!StrKit.isBlank(result_code) && "SUCCESS".equals(result_code)) {
                String prepay_id = (String) result.get("prepay_id");
                Map<String, String> packageParams = new HashMap();
                packageParams.put("appId", Constants.APP_ID); // 这里应该是自己的AppId
                packageParams.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000L));
                packageParams.put("nonceStr", String.valueOf(System.currentTimeMillis()));
                packageParams.put("package", "prepay_id=" + prepay_id);
                packageParams.put("signType", "MD5");
                String packageSign = PaymentKit.createSign(packageParams, "");
                packageParams.put("paySign", packageSign);
                String jsonStr = JsonUtils.toJson(packageParams);
                setAttr("json", jsonStr);
                // 查看一下字符串信息
                System.out.println(jsonStr);
                /**
                 * 生成订单 等逻辑信息
                 **/
                // 这里需要嫁接一层, 从而让前端页面发送 支付请求 。
                set("money", Integer.valueOf(money) / 100);//100
                set("openid", openId);
                render("/configRecharge.jsp");
            } else {
                renderText(return_msg);
            }
        } else {
            renderText(return_msg);
        }
    }

    /**
     * 支付结果通知
     */
    public void pay_notify() {
        // 支付结果通用通知文档: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7
        String xmlMsg = HttpKit.readData(getRequest());
        System.out.println("支付通知=" + xmlMsg);
        Map<String, String> params = PaymentKit.xmlToMap(xmlMsg);
        String result_code = params.get("result_code");
        // 注意重复通知的情况,同一订单号可能收到多次通知,请注意一定先判断订单状态
        // 避免已经成功、关闭、退款的订单被再次更新
        if (PaymentKit.verifyNotify(params, "")) { // 这里被双引号引用起来的是: paternerKey
            if (("SUCCESS").equals(result_code)) {
                //更新订单信息 等操作
                System.out.println("更新订单信息");
                Map<String, String> xml = new HashMap<>();
                xml.put("return_code", "SUCCESS");
                xml.put("return_msg", "OK");
                renderText(PaymentKit.toXml(xml));
                return;
            }
        }
        renderText("");
    }

configRecharge.jsp 这个页面主要是调用js进行支付操作,如果成功了应该如何处理,如果失败了改如何处理,全看这个页面上的操作。

<a id="showTooltips"   onclick="recharge()">确定</a>

js如下 :

<script type="text/javascript">
    function recharge() {
        onBridgeReady() ;
    }
    function onBridgeReady(){
        WeixinJSBridge.invoke(
            'getBrandWCPayRequest',
            ${json},
            function(res){
                // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回    ok,但并不保证它绝对可靠。
                if(res.err_msg == "get_brand_wcpay_request:ok" ) {
                   // 支付成功
                }else{
                   // 支付失败 
                }
            }
        );
    }
    // if (typeof WeixinJSBridge == "undefined"){
    //     if( document.addEventListener ){
    //         document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
    //     }else if (document.attachEvent){
    //         document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
    //         document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
    //     }
    // }else{
    //     onBridgeReady();
    // }
</script>
隐藏/显示右上角按钮

有时候需要显示对应的右上角的按钮,那么就不用操作了;如果要不显示对应的按钮,可以使用如下方法进行操作。

  <script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.4.0.js"></script>
  <script type="text/javascript" src="../../pages/js_sdk.jsp?test=false"></script>
    <script type="text/javascript">
        document.addEventListener('WeixinJSBridgeReady', function onBridgeReady() {
            // 通过下面这个API隐藏右上角按钮
            WeixinJSBridge.call('hideOptionMenu');
        });
    </script>

注意:只能隐藏“传播类”和“保护类”按钮。但是微信推荐的方法如下 :

wx.hideMenuItems({
menuList: [] // 要隐藏的菜单项,只能隐藏“传播类”和“保护类”按钮
});

发现使用这种方式并不能实现其效果。

转载于:https://my.oschina.net/lujiapeng/blog/3055417

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值