塑封玉米蒸多久可以吃,...dvconstraint 大量下拉框 -爱游戏平台

苗坤旺离型膜

安装pillow

pip install pillow

构建图像

image.open(fp, mode =’r’ ):打开图片文件,返回一个image对象

fp:图片路径mode:模式。如果给出,必须是r

from pil import image

im = image.open(path)

image.alpha_composite(im1, im2):在im1对象上的透明层复合im2,返回一个image对象

im1:image对象1im2:image对象2

from pil import image

im1 = image.open(path1)

im2 = image.open(path2)

im3 = image.alpha_composite(im1,im2)

image.blend(im1, im2, alpha):在两个图片对象之间进行插值,返回一个image对象

im1:image对象1im2:image对象2alpha:透明图

如果alpha为0.0,则返回第一个图像的副本。如果alpha为1.0,则返回第二个图像的副本,基本的算法如下:

out = image1 * (1.0 - alpha ) image2 * alpha

image.eval(image, *args):将函数应用于给定图像的中每一个像素。请注意,该函数对每个可能的像素值都进行一次评估,因此您不能使用随机组件或其他生成器。返回一个image对象

image:image对象args:一个函数对象和该函数的一个取整参数

from pil import image

def func(a):

return a

im1 = image.open(path1)

img = image.eval(img1,func,1)

image.merge(mode, bands):将一组单波段图像合并成为一个多波段图像。返回一个image对象

mode:用于输出图像的模式。支持的模式请看下方pillow支持的模式表bands:输出图像中每个波段包含一个单波段图像的序列image.new(mode, size, color=0):根据模式、大小和颜色创建一个新的image对象。烦会一个image对象

mode:用于新图像的模式。支持的模式请看下方pillow支持的模式表size: 大小,元组类型,包含宽度与高度color:用于新图像的颜色。传入一个整数的单波段模式,或传入一个元组的多波段模式,或者传入一个imagecolor对象

from pil import image

# 单个整数值

img = image.new("rgba",(1024,768),215)

# 元组

img = image.new("rgba",(1024,768),(215,0,0)

# imagecolor

from pil import imagecolor

color = imagecolor.getrgb("#ff0000")

img = image.new("rgba",(1024,768),color)

img.show()

从上面代码运行结果显示是一个红色,1024*768的图像

图像对象

alpha_composite(im, dest=(0,0), source=(0,0)):在image对象中符合im,效果与类方法alpha_composite相似。无返回值

im:image对象dest:指定此(目标)图像左上角的可选的长度为2的元组(左,上)source:盖源图像中左上角的长度为2的元组(左,上)或源矩形边界的长度为4的元组(左,上,右,下)copy():复制此图片

from pil import image

img = image.new("rgba",(1024,768),215)

img_copy = img.copy()

crop(box=none):返回此图像的一个矩形区域,为一个image对象

box:裁剪矩形,为一个长度为4的元组(左,上,右,下)

from pil import image

img = image.new("rgba",(1024,768),215)

img_copy = img.crop(box=(0,0,500,500))

draft(mode, size):配置图像文件加载器,以便返回尽可能接近给定模式和大小的图像版本,无返回值

mode:模式size:大小filter(filter):使用给定的过滤器过滤此图像,返回一个image对象

filter:过滤器getbands():获取此图像中每个波段名称的元组。返回一个tuple

from pil import image

img = image.new("rgba",(1024,768),215)

print img.getbands() # ('r', 'g', 'b', 'a')

getbbox():计算图像中非零区域的边界框,返回一个tuple

from pil import image

img = image.new("rgba",(1024,768),215)

print img.getbbox() # (0, 0, 1024, 768)

getcolors(maxcolors=256):返回此图像中使用的颜色列表,返回一个计算与像素元组组成的元组列表

maxcolors: 最大颜色数量,超过此值,当前方法将返回none

from pil import image

img = image.new("rgba",(1024,768),215)

print img.getcolors() # [(786432, (215, 0, 0, 0))]

getdata(band=none):以包含像素值的序列对象的形式返回此图像的内容。返回一个可迭代对象。

band:波段,默认是获取所有。如果需要获取单波段,传入索引值(例如0从“rgb”图像中获得“r”波段)。

from pil import image

img = image.new("rgba",(1024,768),215)

for item in img.getdata():

print item

# 打印结果:

(215, 0, 0, 0)

(215, 0, 0, 0)

(215, 0, 0, 0)

...

getextrema():获取每个波段的最小和最大像素值。对于单波段图像,返回一个长度为2的元组。对与多波段图像,每个波段包含一个2元组。

from pil import image

img = image.new("rgba",(1024,768),215)

print img.getextrema() # ((215, 215), (0, 0), (0, 0), (0, 0))

getpalette():返回图像的调色板,返回一个list对象。如果没有调色板,则返回none

from pil import image

img = image.new("rgba",(1024,768),215)

print img.getpalette() # none

getpixel(xy):返回给定位置的像素值,返回一个tuple

xy:位置,以(x,y)给出的坐标。

from pil import image

img = image.new("rgba",(1024,768),215)

print img.getpixel((500,500)) # (215, 0, 0, 0)

histogram(mask=none, extrema=none):返回图像的直方图。直方图以像素计数列表的形式返回,返回一个列表。

mask:掩码extrema:极值paste(im, box=none, mask=none):将im图像粘贴到此图像上面。无返回值

box:box可以是图像左上角的长度为2的元组(左,上)或长度为4的元组(左,上,右,下)mask:掩码

import os

from pil import image

path1 = os.path.join(os.getcwd(),"23.png")

img1 = image.open(path1)

img = image.new("rgba",(1024,768),215)

img1.paste(img)

img1.show()

putdata(data, scale=1.0, offset=0.0):将像素数据复制到此图像上面。从图像左上角开始,直到图像或序列结束,无返回值。比例和偏移值用于调整序列值:pixel = value * scale offset。

data:一个图像数据序列scale:缩放比例值offset:偏移量值

from pil import image

img = image.new("rgba",(1024,768),215)

img_c = image.new("rgba",(1024,768),-100)

img.putdata(img_c.getdata())

img.show()

putpalette(data, rawmode=’rgb’):附加一个调色板到这个图像。图像的模式必须是p或者l。返回一个image对象

data:调色板序列rawmode:调色板的原始模式

from pil import image

img = image.new("p",(1024,768),215)

img_c = image.new("p",(1024,768),-100)

img_c.putpalette(img.getpalette())

img_c.show()

quantize(colors=256, method=none, kmeans=0, palette=none):将图像转换为具有指定数量的颜色的p模式图像,返回一个image对象

colors:所需颜色的数量,<=256method:0:中值切割,1:最大覆盖率,2:快速八叉树,3:libimagequantkmeans:整数palette:量化给定的调色板

from pil import image

img = image.new("rgba",(1024,768),215)

img_q = img.quantize(colors=256,method=2)

print img_q #

resize(size, resample=0, box=none):返回此图像的调整大小后的副本。返回一个image对象

size:以像素为单位的长度为2的元组(宽度,高度)resample:重采样滤波器。可以设置为:image.nearest、image.box、image.bilinear、image.hamming、image.bicubic或者image.lanczos。如果省略,或者图像模式为1或者p,则设置image.nearest。box:一个浮点数组成的长度为4的元组,给出应该缩放的源图像的区域。值应该在(0,0,宽度,高度)的矩形内。

from pil import image

img = image.new("rgba",(1024,768),215)

img_r = img.resize(size=(500,500))

print img_r #

rotate(angle, resample=0, expand=0, ceter=none, translate=none):旋转图像,并返回旋转后的图像副本。返回image对象

angle:角度,逆时针旋转resample:重采样滤波器。可以是:image.nearest、image.bilinear或者image.bicubic。如果省略,或者图像模式为1或者p,则设置image.nearest。expand:是否展开。如果为true,则展开输出图像以使其足够大以容纳整个旋转后的图像。如果为false或省略,使输出图像的大小与输入图像相同。center:旋转中心,长度为2的元组(宽度,高度),原点是左上角,默认是图像的中心translate:旋转后。一个长度为2的元组(宽度,高度)

import os

from pil import image

path1 = os.path.join(os.getcwd(),"23.png")

img1 = image.open(path1)

img_r = img1.rotate(45,image.bicubic)

img_r.show()

可以看到,图像已经逆时针旋转了45度

save(fp, format=none, **params):保存图像到给定的文件名下。如果没有指定格式,则可以使用文件扩展名来确定要使用的格式。无返回值

fp:文件名或路径format:可选的格式覆盖

import os

from pil import image

path1 = os.path.join(os.getcwd(),"23.png")

img1 = image.open(path1)

img_r = img1.rotate(45,image.bicubic)

img_r.save(os.path.join(os.getcwd(),"rotate.png"))

seek(frame):在这个序列文件中寻找给定的帧。如果您在序列结束之外寻找方法,则会 引发eoferror异常。当序列文件被打开时,库会自动寻找0帧。无返回值

frame:帧号。从0开始show(title=none, command=none):显示这个图像,此方法主要用于调试目的。无返回值

title:在可能的情况下,用于图像窗口的可选标题。command:用于显示图像的命令split():将图像分割成单独的波段。该方法从图像中返回一个单独的图像的元组。例如,拆分“rgb”图像会创建三个新图像,每个图像都包含原始波段(红色,绿色,蓝色)之一的副本。返回一个tuple

from pil import image

path1 = os.path.join(os.getcwd(),"23.png")

img1 = image.open(path1)

data = img1.split()

print data # (, , , )

getchannel(channel):返回包含源图像的单个通道的图像。返回l模式的图像,返回一个image对象

channel:返回什么频道的图像。可以是索引(“rgba”的”r”通道为0)或通道名称(“rgba”的alpha通道为”a”)

from pil import image

path1 = os.path.join(os.getcwd(),"23.png")

img1 = image.open(path1)

im = img1.getchannel(0)

或者:

im = img1.getchannel("r")

tell():获取当前的帧号。返回intthumbnail(size, resample=3):将此图像制作成缩略图。该方法修改图像以包含其本身的缩略图版本,不大于给定尺寸。无返回值

size:大小resample:重采样滤波器。可以是:image.nearest、image.bilinear、image.bicubic或者image.lanczos。如果省略,则默认为image.bicubic

from pil import image

path1 = os.path.join(os.getcwd(),"23.png")

img1 = image.open(path1)

img1.thumbnail(size=(500,500),resample=image.bicubic)

print img1 #

tobitmap(name=’image’):返回转换为x11位图的图像。此方法只使用于模式为1的图像,返回一个str

name:用于位图变量的前缀名称

from pil import image

img = image.new("1",(1024,768),215)

data = img.tobitmap(name='abc')

print data

# 结果如下:

"""

#define abc_width 1024

#define abc_height 768

static char abc_bits[] = {

0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,

0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,

0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,

0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,

...

};

"""

tobytes(encoder_name=’raw’, *args):以图像作为字节对象返回。为一个str对象transpose(method):旋转或翻转图像,返回旋转或翻转后的图像副本,一个image对象

method:可以是:image.flip_left_right、image.flip_top_bottom、image.rotate_90、image.rotate_180、image.rotate_270、image.transpose或者image.transverse

from pil import image

path1 = os.path.join(os.getcwd(),"23.png")

img1 = image.open(path1)

im = img1.transpose(image.flip_left_right)

im.show()

可以看出图像已经翻转了

close():关闭文件指针

图像对象属性

filename:源文件的文件名或路径。只有通过open方法构建的图像对象才具有此属性

import os

from pil import image

path1 = os.path.join(os.getcwd(),"23.png")

img = image.open(path1)

print img.filename # 、/aaa/bbb/ccc/23.png

format:源文件的图片格式。对于由库自身创建的图像,此属性值为none

import os

from pil import image

path1 = os.path.join(os.getcwd(),"23.png")

img = image.open(path1)

print img.format # png

img = image.new("rgba",(1024,768),215)

print img.format # none

mode:图像模式。这是一个字符串,指定图像使用的像素格式。

from pil import image

img = image.new("rgba",(1024,768),215)

print img.mode # rgba

size:图像大小,以像素为单位。大小以长度为2的元组(宽度,高度)给出。类型tuple

from pil import image

img = image.new("rgba",(1024,768),215)

print img.size # (1024, 768)

width:图像宽度,以像素为单位。类型int

from pil import image

img = image.new("rgba",(1024,768),215)

print img.width # 1024

height:图像高度,以像素为单位。类型int

from pil import image

img = image.new("rgba",(1024,768),215)

print img.height # 768

palette:调色板表。如果模式为p,这应该是imagepalette类的一个实例。否则为none

from pil import image

img = image.new("rgba",(1024,768),215)

print img.palette # none

img = image.new("p",(1024,768),215)

print img.palette #

info:保存与图像相关的数据的字典。这个字典被文件处理程序用来传递从文件读取的各种非图像信息。

import os

from pil import image

path1 = os.path.join(os.getcwd(),"23.png")

img = image.open(path1)

print img.info

# 结果如下:

'''

{

'chromaticity': (0.31269, 0.32899, 0.63999, 0.33001, 0.3, 0.6, 0.15, 0.05999),

'icc_profile': 'xxxx/...',

'dpi': (300, 300)

}

'''

img = image.new("rgba",(1024,768),215)

print img.info # {}

pillow支持的模式表

模式说明11位像素,黑白,每字节一个像素存储l8位像素,黑白p8位像素,使用调色板映射到任何其他模式rgb3x8位像素,真彩色rgba4×8位像素,带透明度掩模的真彩色cmyk4x8位像素,分色ycbcr3x8位像素,彩色视频格式lab3×8位像素,l * a * b颜色空间hsv3x8位像素,色调,饱和度,值颜色空间i32位有符号整数像素f32位浮点像素

更多关于image的操作:http://pillow.readthedocs.io/en/latest/reference/image.html

工具类

import org.apache.poi.hssf.usermodel.*;

import org.apache.poi.ss.usermodel.name;

import org.apache.poi.ss.util.cellrangeaddresslist;

import java.util.arraylist;

import java.util.list;

import java.util.map;

/**

* @classname excelout

* @description: todo

* @author xianyu

* @date 2022/01/14

* @version v1.0

**/

public class excelout {

/**

* excel导出,有码值的数据使用下拉框展示。解决下拉框最多255个字符的问题。

* 原理为新建一个隐藏状态的sheet页,用来存储下拉框的值。

*

* @param wb    工作簿 hssfworkbook

* @param boxmap   码值集合

* @param rows   正常sheet页数据,用来指定哪些行需要添加下拉框

* @param i  多个码值需要添加下拉,隐藏状态的sheet页名称不能重复,添加i值区分。

* @param coltoindex 用来指定哪些列需要添加下拉框

* @return datavalidation

*/

public static list createbox1(hssfworkbook wb,map boxmap, int rows, int i, int coltoindex) {

list list =new arraylist<>();

hssfdatavalidation datavalidation = null;

string cols1 = "";

//查询码值集合,获取当前列的码值。

if (null != boxmap.get("zzlx")) {

cols1 = boxmap.get("zzlx");

}

//第一列 组织类型

string str1[] = cols1.split(",");

//指定0-9行,0-0列为下拉框

cellrangeaddresslist cas = new cellrangeaddresslist(1 , 99999 , 0 , 0);

//创建下拉数据列

dvconstraint dvconstraint = dvconstraint.createexplicitlistconstraint(str1);

//将下拉数据放入下拉框

datavalidation = new hssfdatavalidation(cas, dvconstraint);

list.add(datavalidation);

string cols2 = "";

//查询码值集合,获取当前列的码值。

if (null != boxmap.get("glqy")) {

cols2 = boxmap.get("glqy");

}

//新建隐藏状态的sheet,用来存储码值。

if (cols2.length() > 0 && null != cols2) {

//管理区域

string str[] = cols2.split(",");

//创建sheet页

hssfsheet sheet = wb.createsheet("hidden" i);

//向创建的sheet页添加码值数据。

for (int i1 = 0; i1 < str.length; i1 ) {

hssfrow row = sheet.createrow(i1);

hssfcell cell = row.createcell((int) 0);

cell.setcellvalue(str[i1]);

}

//将码值sheet页做成excel公式

name namedcell = wb.createname();

namedcell.setnamename("hidden" i);

namedcell.setreferstoformula("hidden" i "!$a$1:$a$" str.length);

//确定要在哪些单元格生成下拉框

dvconstraint = dvconstraint.createformulalistconstraint("hidden" i);

cellrangeaddresslist regions = new cellrangeaddresslist(1, rows, coltoindex, coltoindex);

datavalidation = new hssfdatavalidation(regions, dvconstraint);

list.add(datavalidation);

//隐藏码值sheet页

int sheetnum = wb.getnumberofsheets();

for (int n = 1; n < sheetnum; n ) {

wb.setsheethidden(n, true);

}

}

string cols3 = "";

//查询码值集合,获取当前列的码值。

if (null != boxmap.get("sjzz")) {

cols3 = boxmap.get("sjzz");

}

//上级组织

string str[] = cols3.split(",");

//指定0-9行,0-0列为下拉框

cellrangeaddresslist cas2 = new cellrangeaddresslist(1 , 99999 , 3 , 3);

//创建下拉数据列

dvconstraint dvconstraint2 = dvconstraint.createexplicitlistconstraint(str);

//将下拉数据放入下拉框

datavalidation = new hssfdatavalidation(cas2, dvconstraint2);

list.add(datavalidation);

return list;

}

}

调用

@override

public void importexcel(httpservletresponse response) throws unsupportedencodingexception {

log.info("导入模板下载");

hssfworkbook hssfworkbook = new hssfworkbook();

hssfsheet sheet = hssfworkbook.createsheet();

hssfrow row = sheet.createrow(0);

row.createcell(0).setcellvalue("组织类型");

row.createcell(2).setcellvalue("管理区域");

row.createcell(1).setcellvalue("组织名称");

row.createcell(3).setcellvalue("上级组织");

map boxmap = new hashmap<>();

boxmap.put("zzlx", "运营监管,养老机构,养老院,服务中心,服务商,评估机构,志愿者团队");

//查询所有区域

list arealist = sysareamapper.findareaname();

string areaname = stringutils.join(arealist.toarray(), ",");

boxmap.put("glqy", areaname);

//查询现有机构

list namelist = organtreemapper.findnamelist();

string organname = stringutils.join(namelist.toarray(), ",");

boxmap.put("sjzz", organname);

int i = 0;

list datavalidationlist = excelout.createbox1(hssfworkbook, boxmap, 99999, i, 1);

if (!collectionutils.isempty(datavalidationlist)) {

for (hssfdatavalidation hssfdatavalidation : datavalidationlist) {

sheet.addvalidationdata(hssfdatavalidation);

}

}

string filepath="组织机构导入模板.xls";

//通过浏览器下载

response.reset();//清除buffer缓存

response.setcontenttype("application/vnd.ms-excel;charset=utf-8");

response.setheader("content-disposition", "attachment; filename=" new string(filepath.getbytes("utf-8"), "iso-8859-1"));// 定义文件名

response.setheader("pragma", "no-cache");

response.setheader("cache-control", "no-cache");

response.setheader("expires", " 0");

try {

outputstream output;

try {

output=response.getoutputstream();

bufferedoutputstream bufferoutput =new bufferedoutputstream(output);

bufferoutput.flush();

hssfworkbook.write(bufferoutput);

bufferoutput.close();

} catch (exception e) {

}

} catch (exception e) {

}

}

目录

背景准备工作,注册获取开发参数开发下单接口回调接口查询订单接口

背景

项目中本来是使用微信和支付宝分开的收款码收款,现在需要实现一张二维码图片,微信/支付宝 都能扫码付款 这里可以先看看自己支付宝或者微信的费率,我们公司直接对接支付宝和微信的费率分别的0.6%和0.9%,后来了解到一些聚合支付的通道能给到0.38%,真香。 开始选型,要求是文档清晰,长期稳定,费率低,安全可靠。 这里选的是【计全付】,开始找了一些其它的要么费率高,不靠谱,弄得我想自己写个聚合支付了,在gitee找代码时看到的,star还挺高的就看了下 计全付码云仓库 sdk地址 jeepay-plus文档 文档还算详尽,这里做个记录

准备工作,注册获取开发参数

注册地址 注册–>登录 目前商家推荐使用盛付通

获取商户号 获取appid 获取私钥

开发

引入sdk

com.jeequan

jeepay-sdk-java

1.5.0

为了方便统一管理,添加配置文件 回调地址是自己本地的,后面会用到

调用sdk每次都需要new一个jeepayclient,并赋值,这里为了方便,写在配置文件里面

import com.jeequan.jeepay.jeepay;

import com.jeequan.jeepay.jeepayclient;

import org.springframework.beans.factory.annotation.autowired;

import org.springframework.context.annotation.bean;

import org.springframework.context.annotation.configuration;

import org.springframework.context.annotation.propertysource;

import org.springframework.core.env.environment;

/**

* @author:xianyu

* @createdate:2022/8/5

* @description:

*/

@configuration

@propertysource("classpath:jee-pay.properties")

public class jeepayclientconfig {

@autowired

private environment config;

@bean

public jeepayclient jeepayconfig(){

jeepay.setapibase(config.getproperty("api-base"));

jeepay.apikey = config.getproperty("api-key");

jeepay.mchno = config.getproperty("mch-no");

jeepay.appid = config.getproperty("app-id");

jeepayclient jeepayclient = jeepayclient.getinstance(jeepay.appid, jeepay.apikey, jeepay.getapibase());

return jeepayclient;

}

}

注意classpath:后面接自己配置文件的名称,按住ctrl能跳转就说明没问题

下单接口

编写扫码下单接口,这里实体类就不贴出了,可参考开发文档里面自定义参数即可

controller

import com.jeequan.jeepay.exception.jeepayexception;

import com.mcsgis.saas.common.data.r;

import com.mcsgis.saas.system.api.domain.dto.orderinfodto;

import com.mcsgis.saas.yun.service.jeepayservice;

import io.swagger.annotations.api;

import io.swagger.annotations.apioperation;

import io.swagger.annotations.apiparam;

import org.springframework.beans.factory.annotation.autowired;

import org.springframework.web.bind.annotation.*;

import java.util.map;

/**

* @author:xianyu

* @createdate:2022/8/5

* @description:

*/

@api(tags = "计全聚合支付")

@restcontroller()

public class jeepaycontroller {

@autowired

private jeepayservice jeepayservice;

/**

* 扫码下单

*

* @param orderinfodto

* @return

* @throws jeepayexception

*/

@postmapping("/scanpay")

public string scanpay(@requestbody orderinfodto orderinfodto) throws jeepayexception {

return jeepayservice.scanpay(orderinfodto);

}

}

service

/**

* 统一生成订单接口,返回二维码

*

* @param orderinfodto

* @return

* @throws jeepayexception

*/

string scanpay(orderinfodto orderinfodto) throws jeepayexception;

serviceimpl

import cn.hutool.http.httprequest;

import com.alibaba.fastjson.json;

import com.alibaba.fastjson.jsonobject;

import com.jeequan.jeepay.jeepay;

import com.jeequan.jeepay.jeepayclient;

import com.jeequan.jeepay.exception.jeepayexception;

import com.jeequan.jeepay.model.payordercreatereqmodel;

import com.jeequan.jeepay.request.payordercreaterequest;

import com.jeequan.jeepay.response.payordercreateresponse;

import com.jeequan.jeepay.util.jeepaykit;

import com.mcsgis.saas.common.constant.jeepayconstants;

import com.mcsgis.saas.common.util.jsonutil;

import com.mcsgis.saas.system.api.domain.dto.orderinfodto;

import com.mcsgis.saas.yun.configuration.jeepayclientconfig;

import com.mcsgis.saas.yun.service.jeepayservice;

import lombok.extern.slf4j.slf4j;

import org.springframework.beans.factory.annotation.autowired;

import org.springframework.core.env.environment;

import org.springframework.stereotype.service;

import sun.security.krb5.internal.aprep;

import java.math.bigdecimal;

import java.util.date;

import java.util.hashmap;

import java.util.map;

import java.util.objects;

/**

* @author:xianyu

* @createdate:2022/8/5

* @description:

*/

@slf4j

@service

public class jeepayserviceimpl implements jeepayservice {

@autowired

private jeepayclient jeepayclient;

@autowired

private environment config;

@override

public string scanpay(orderinfodto orderinfodto) {

// 构建请求数据

payordercreaterequest request = new payordercreaterequest();

payordercreatereqmodel model = new payordercreatereqmodel();

// 商户号

model.setmchno(jeepay.mchno);

// 应用id

model.setappid(jeepay.appid);

// 商户订单号

model.setmchorderno(orderinfodto.getouttradeno());

// 爱游戏官网登录入口的支付方式

model.setwaycode(jeepayconstants.qr_cashier);

// 金额,单位分

long amount = orderinfodto.gettotalamount().multiply(new bigdecimal(100)).longvalue();

model.setamount(amount);

// 币种,目前只支持cny

model.setcurrency("cny");

// 发起支付请求客户端的ip地址

model.setclientip(config.getproperty("ip-address"));

// 商品标题

model.setsubject(orderinfodto.getsubject());

// 商品描述

model.setbody("商品描述");

// 异步通知地址

model.setnotify);

// 前端跳转地址

model.setreturn;

// 渠道扩展参数

model.setchannelextra(channelextra(jeepayconstants.qr_cashier));

// 商户扩展参数,回调时原样返回

model.setextparam("");

request.setbizmodel(model);

log.info("jeepay下单参数处理完毕,参数:[{}]", json.tojsonstring(request));

string result = "failure";

try {

payordercreateresponse response = jeepayclient.execute(request);

// 下单成功

if (response.issuccess(jeepay.apikey)) {

result = response.getdata().getstring("paydata");

} else {

log.warn("下单失败:{}", orderinfodto.getouttradeno());

}

} catch (jeepayexception e) {

log.error(e.getmessage());

}

return result;

}

string channelextra(string waycode) {

if ("wx_lite".equals(waycode)) return wxjsapiextra();

if ("wx_jsapi".equals(waycode)) return wxjsapiextra();

if ("wx_bar".equals(waycode)) return wxbarextra();

if ("ali_bar".equals(waycode)) return alibarextra();

if ("ysf_bar".equals(waycode)) return ysfbarextra();

if ("upacp_bar".equals(waycode)) return upacpbarextra();

if ("qr_cashier".equals(waycode)) return qrcashierextra();

if ("auto_bar".equals(waycode)) return autobarextra();

if ("pp_pc".equals(waycode)) return ppextra();

if ("sand_h5".equals(waycode)) return sandh5extra();

return "";

}

private string wxjsapiextra() {

jsonobject obj = new jsonobject();

obj.put("openid", "134756231107811344");

return obj.tostring();

}

private string wxbarextra() {

jsonobject obj = new jsonobject();

obj.put("authcode", "134675721924600802");

return obj.tostring();

}

private string alibarextra() {

jsonobject obj = new jsonobject();

obj.put("authcode", "1180812820366966512");

return obj.tostring();

}

private string ysfbarextra() {

jsonobject obj = new jsonobject();

obj.put("authcode", "6223194037624963090");

return obj.tostring();

}

private string upacpbarextra() {

jsonobject obj = new jsonobject();

obj.put("authcode", "6227662446181058584");

return obj.tostring();

}

private string qrcashierextra() {

jsonobject obj = new jsonobject();

obj.put("paydatatype", "codeimgurl");

return obj.tostring();

}

private string autobarextra() {

jsonobject obj = new jsonobject();

obj.put("authcode", "134753177301492386");

return obj.tostring();

}

private string ppextra() {

jsonobject obj = new jsonobject();

obj.put("cancelurl", "http://baidu.com");

return obj.tostring();

}

private string sandh5extra() {

jsonobject obj = new jsonobject();

jsonobject payextra = new jsonobject();

// 聚合码

obj.put("productcode", "02000001");

obj.put("payextra", "");

obj.put("metaoption", "[{\"s\":\"pc\",\"n\":\"支付\"}]");

// 微信公众号

/*obj.put("productcode", "02010002");

payextra = new jsonobject();

payextra.put("mer_app_id", "");

payextra.put("openid", "");

obj.put("payextra", payextra.tostring());

obj.put("metaoption", "");

// 微信小程序(云函数sdk)

obj.put("productcode", "02010007");

payextra = new jsonobject();

payextra.put("wx_app_id", ""); // 移动应用appid(微信开放平台获取,wx开头)

payextra.put("gh_ori_id", ""); // 小程序原始id(微信公众平台获取,gh_开头)

payextra.put("path_url", ""); // 拉起小程序页面的可带参路径,不填默认拉起小程序爱游戏平台首页

payextra.put("miniprogramtype", "0"); // 开发时根据小程序是开发版、体验版或正式版自行选择。正式版:0; 开发版:1; 体验版:2

obj.put("payextra", payextra.tostring());

obj.put("metaoption", "");

// 支付宝生活号

obj.put("productcode", "02010002");

payextra = new jsonobject();

payextra.put("buyer_id", ""); // 支付宝生活号所需参数(支付宝h5建议不传)

obj.put("payextra", payextra.tostring());

obj.put("metaoption", "");*/

return obj.tostring();

}

}

在配置文件中给 jeepayclient 初始值了,这里直接用@autowired引入即可,不需要额外配置

测试下单接口,返回值

{ “status”: 200, “msg”: “操作成功”, “data”: “https://pay.jeepay.vip/api/scan/imgs/a034b9a3cc3e89d53d936c196d5a48c219c1f01a8623733b004429d057af027e69b4220ccdc8ad29375295f30ccb3e1cf130261adeb28c64f27dc682305ec082062889a910c12dba1317fa3f69c29eb0c2c83f71516c305b25830bf4a83e21abb235934310960a27aef6f6868c4a1481c52f213b693c30c0c391ecd371e17e0bfbc0f77203beabcb7549c44df66de99d06ad9d731e2ebac14a43effcec80aace.png” }

返回的是一个图片地址,复制到浏览器中

微信扫码测试

支付宝扫码测试

回调接口

业务系统处理后同步返回给支付中心,返回字符串 success 则表示成功,返回非success则表示处理失败,支付中心会再次通知业务系统。(通知频率为0/30/60/90/120/150,单位:秒)

支付完成之后,我们的业务系统遇到知道用户已经付款,这里就有一个要使用回调,第四方商家通知我们的业务系统,我们开发普遍都在内网,这里就需要使用一个内网穿透,我用的是 ngrok

回调接口

@apioperation("支付回调")

@postmapping("/tradenotify")

public string tradenotify(httpservletrequest req) {

return orderinfoservice.tradenotify(req);

}

@override

public string tradenotify(httpservletrequest req) {

string result = "failure";

try {

map map = getparamsmap(req);

log.info("支付回调参数处理完毕,参数:[{}]", json.tojsonstring(map));

//获取私钥

string s = redistemplate.opsforvalue().get(sysconfigconstants.jee_pay);

jsonobject configobj = new jsonobject();

string apikey;

if (strutil.isnotblank(s)) {

configobj = jsonobject.parseobject(s);

apikey = configobj.get("api-key").tostring();

} else {

sysconfiguration configuration = sysconfigurationmapper.findbykey(sysconfigconstants.jee_pay);

object config = configuration.getconfig();

redistemplate.opsforvalue().set(sysconfigconstants.jee_pay, config.tostring(), sms_expires, timeunit.milliseconds);

configobj = jsonobject.parseobject(config.tostring());

apikey = configobj.get("api-key").tostring();

}

//验签

if (chacksgin(map, apikey)) {

return result;

}

log.info("回调验签处理完成,开始处理业务逻辑,参数:[{}]", json.tojsonstring(map));

orderinfo info = new orderinfo();

info.setouttradeno(map.get("mchorderno").tostring());

info.setispay(true);

updatebyprimarykeyselective(info);

result = "success";

} catch (exception e) {

e.printstacktrace();

}

return result;

}

我这里的私钥放在数据库中,放在配置文件的不用可以直接取,不用查库或者redis

验签

/**

* 回调验签

*

* @param result

* @param map

* @param apikey

* @return

*/

private boolean chacksgin(map map, string apikey) {

object sign = map.remove("sign");

string resign = jeepaykit.getsign(map, apikey);

log.info("调用sdk加签,返回参数:[{}]", resign);

if (!objects.equals(resign, sign)) {

log.error("支付成功,异步通知验签失败!");

return true;

}

log.info("支付成功,异步通知验签成功!");

//todo 验签成功后,按照支付结果异步通知中的描述,对支付结果中的业务内容进行二次校验

//1.验证mchorderno 是否为商家系统中创建的订单号

list orderinfos = orderinfomapper.selectbyouttradeno(map.get("mchorderno").tostring());

log.info("支付成功回调,查询订单,[{}]", json.tojsonstring(orderinfos));

if (collectionutil.isempty(orderinfos)) {

log.error("支付成功,回调通知,mchorderno不是本系统生成的订单号!!");

return true;

}

//2.判断 amountt 是否确实为该订单的实际金额

//订单金额单位转换为分

bigdecimal reduce = orderinfos.stream().map(e -> e.getprice()).reduce(new bigdecimal("0"), bigdecimal::add).multiply(new bigdecimal(100));

bigdecimal amount = new bigdecimal(map.get("amount").tostring());

if (reduce.compareto(amount) != 0) {

log.error("支付成功,回调通知,订单金额与实际金额不符!!");

return true;

}

//todo 后续添加 2022-08-16

//3.校验通知中的 mchno 是否是本条

//4.验证 app_id 是否为该商家本身

return false;

}

private map getparamsmap(httpservletrequest req) {

map requestmap = req.getparametermap();

map paramsmap = new hashmap<>();

requestmap.foreach((key, values) -> {

string strs = "";

for (string value : values) {

strs = strs value;

}

paramsmap.put(key, strs);

});

return paramsmap;

}

这里要说下验签,文档中给出的签名算法如下 这里只做了支付平台的比对,支付宝的文档中还需要与本地系统对比做二次校验

验签成功后,按照支付结果异步通知中的描述,对支付结果中的业务内容进行二次校验 1.验证mchorderno 是否为商家系统中创建的订单号 2.判断 amountt 是否确实为该订单的实际金额 3.校验通知中的 mchno 是否是本条 4.验证 app_id 是否为该商家本身

只要这4条有一条不对就不能返回success

查询订单接口

/**

* 查询订单状态

*

* @param outtradeno

* @return 0-订单生成 1-支付中 2-支付成功 3-支付失败 4-已撤销 5-已退款 6-订单关闭

* @throws exception

*/

@apioperation("查询订单状态")

@getmapping("/query")

public r query(@requestparam("outtradeno") @apiparam("订单编号(唯一)") string outtradeno){

map map = jeepayservice.query(outtradeno);

return r.success(map);

}

@override

public map query(string outtradeno) {

log.info("查询订单状态,outtradeno:{}",outtradeno);

map parmap =new hashmap<>();

parmap.put("mchno",config.getproperty("mch-no"));

parmap.put("appid",config.getproperty("app-id"));

parmap.put("mchorderno",outtradeno);

parmap.put("reqtime",new date().gettime());

parmap.put("version","1.0");

parmap.put("signtype","md5");

string sign = jeepaykit.getsign(parmap, config.getproperty("api-key"));

parmap.put("sign",sign);

log.info("查询订单状态,参数处理完毕,参数:[{}]",json.tojsonstring(parmap));

string body = httprequest.post(config.getproperty("query-url"))

.body(json.tojsonstring(parmap))

.execute()

.body();

map parse = jsonutil.parse(body, map.class);

map datamap =(map) parse.get("data");

log.info("查询订单状态,完成,返回结果:[{}]",json.tojsonstring(datamap));

return datamap;

}

这个接口比较简单,不过有个bug,截止我写完博客也没解决,返回值没有4-已撤销,支付的时候取消了,返回值还是1-支付中,计全付商家给出的解释是支付状态是上游返回,他们也只是原样返回给我这边,不过我自己对接支付宝或者微信的时候没有出现这种情况

分享
文章爱游戏平台的版权声明:除非注明,否则均为苗坤旺离型膜原创文章,转载或复制请以超链接形式并注明出处。

发表评论

快捷回复: 表情:
applausebadlaughcoffeefabulousfacepalmfecesfrownheyhainsidiouskeepfightingnoprobpigheadshockedslapsocialsweattolaughwatermelonwittywowyeahyellowdog
评论列表 (暂无评论,9人围观)

还没有评论,来说两句吧...

微信二维码
微信二维码
支付宝二维码
网站地图