走上“码农”这条路已经快一年半啦,做过不少的Demo和项目,也接触了不少的业务场景,8000w的大数据量处理,QPS500+的并发优化,也算是在开发的这条路上踏出了入门向前的步伐;不过,每个人所处的公司不同,所面对的业务不同,大家逐渐都开始有了自己所擅长处理的业务类型,我也不例外,在经历了疫情期间的跳槽之后,我又开始涉足一个全新的业务领域,而我面对的第一个挑战就是---支付!

在之前的项目经历中,我有了解过支付的时序,流程,但是碍于没有企业身份,没有亲手调通过全流程支付业务,在我之前的《完整电商后台API》(目前还处于鸽子状态的文章)中,我通过调用别人写好的支付中台(中心)用一种类似于接入第三方跳转的方式完成了支付;上一周,我目前所在的项目功能需要接入支付功能,且项目的核心业务需要对支付的过程进行业务干涉,因此,借助别人封装好的支付中心是无法满足我目前的业务需求的,并且,我也想自己完整的实现一次复杂的支付业务,在经历一天的阅读文档和一天的不断调试之后,我成功的调起了微信支付的完整链路实现了小程序端的支付功能,当然在之后,对于订单逻辑的处理以及安全,鉴权等等机制仍然需要代码的继续迭代,但是调通支付就算是走出了第一步,在本文中,我将这两天的踩坑记录分享给大家,初次尝试,代码如有问题,请大家多多指点。

起源

接入微信支付的条件/必需品

如何接入微信支付,其实微信官方和网上有许多教程,过程还是比较复杂的,我在这篇文章里就不过多的赘述了,有需要的伙伴可以私聊我,在这里我简单罗列一下接入支付的必需品:

1、需要接入微信支付首先你需要有一个拥有企业资质的小程序/公众号,公众号是可以挂载小程序的,而且它挂载的小程序也是自动拥有企业资质的。

2、在拥有企业资质之后,你需要接通微信支付功能,需要申请一个商户号,这个商户号呢,和开通企业资质的过程差不太多,都是鉴别身份用,开通之后,有三个东西需要特别关注,第一时间保存设置:1)API安全数字证书,在开通商户号之后,在开发设置里你就可以看到,需要下载官方的工具生成证书,需要妥善保存;2)API密钥;3)商户号

在准备好以上开发必需品之后,我们就开始编写我们的前后端服务吧!

01

使用后台开发脚手架

因为我们想要调通微信支付,我们的整体项目对接口的鉴权还是需要有一定的设计和编码的,无论是使用spring的Security还是Jwt+shiro,编码量都很大,且和我们的主体业务没有实质关联,因此我们这个项目寻找一款后端开发的脚手架renren-fast快速搭建我们的后台项目:

项目地址:(https://gitee.com/renrenio/renren-fast)

框架官网:(https://www.renren.io/)

这是一款轻量级的Spring Boot2.1快速开发平台,整合了Spring Boot、Shiro、MyBatis、Redis、Bootstrap、Vue2.x,可以满足我们常见业务的日常需求

当然目前市面上后端开发脚手架很多,例如:JEECG就是一款优秀的J2EE脚手架,大家可以根据自己的需求选择,当然啦,你手动从零搭建开发环境也完全没有问题,根据业务需求选择而已。

02

接入微信支付流程

首先微信支付根据不同的业务场景,给出了不同的解决方案(API/SDK),使用不同的支付方式,都需要在商户号里面进行开通,才能够使用,这一点在你接通了商户号之后,在控制台便可以看到,在这里我不做过多的陈述;我从官方的开发文档中,根据官方流程得到两张时序图:

1、首先用户在小程序上面点击微信支付按钮。

2、小程序发起Ajax请求,告诉商户系统,你应该向微信平台申请创建支付订单了。

3、接下来商户系统先要提取小程序提交过来的数据,比如说OpenID是否有效,OpenId是用户拿微信登陆小程序产生的ID值。商户系统在数据库里面要记录这个OpenID值。发起付款的时候,商户系统必须要验证一下,小程序提交过来OpenId在数据库里面是不是存在。如果有人拿POSTMAN模拟小程序提交支付请求,胡乱编了一个OpenId,商户系统必须要能分辨出来。还有就是小程序提交过来的订单编号,商户系统也要验证订单的有效性。

4、商户系统要向微信平台发送生成支付订单的请求,并且上传跟支付相关的各种信息。比如说,发起付款的用户,微信的OpenID是什么?订单的金额是什么?人民币付款,还是美元付款?收款的商户ID是什么等等。

5、微信平台收到这些信息之后,核实之后没有问题,那么微信平台上面就会生成支付订单,并且把订单的信息返回给商户系统。

6、商户系统得到订单信息之后,还要对这些信息生成MD5数字签名。然后商户系统,会把支付订单的参数,返回给小程序。这幅时序图走下来,意味着微信平台上面生成支付订单。

大家请看,这幅时序图讲的是用户怎么付款的。刚才说到了,小程序会拿到商户平台返回的支付参数。但是小程序这边也担心,毕竟订单是商户系统生成的,万一生成的支付金额,跟商品订单的金额对不上怎么办?说的难听一点,用户买了300块钱的商品,但是商户系统在微信平台上面创建了一个3000块钱的支付订单。小程序这边是不是得提防商户系统创建高额支付订单,所以还是得拿着支付订单号,到微信平台上面查询一下,究竟这个支付订单的金额是多少钱?

1、小程序把商户系统返回的支付参数,提交给微信平台。

2、微信平台确认小程序提交的支付参数没有问题,于是就把商户创建的支付订单信息返回给小程序,让用户确认。

3、这时候小程序就会弹出支付的金额和收款的商户,以及备注信息

4、用户对支付金额没有异议,于是就输入支付密码或者扫脸,接下来小程序想微信平台发送确认支付的请求

5、微信平台验证用户支付密码正确,就可以执行支付订单了。是从用户零钱里扣除,还是从银行卡里扣款,这个就是微信平台的事情了,跟我们没有关系。

6、微信平台会把支付结果,分别发送给商户系统和小程序。然后用户和商户就都知道付款到底成功,还是失败了。

03

小程序登录

注:因为整体流程相对复杂,代码量也较多,为了篇幅及阅读体验,我在文章中只展示核心业务代码,而且因为代码设计隐私信息,如果这部分代码感兴趣的伙伴,可以私聊我获取源码。

我们先要实现这个支付流程,首先必须要做的是,给小程序实现微信登陆的功能。大家回顾一下第一幅时序图,也就是创建支付订单的那副时序图。首先由小程序发起请求给商户系统,让商户系统申请创建支付订单。各位同学你仔细想一想,如果是POSTMAN软件,或者HTTPClient这样的客户端程序,模拟小程序发出请求,他们根本都不是真实的用户,而且也没有登陆小程序,所以商户系统必须要加以判断。

其实想要判断到底是不是真实的用户发来的请求,我们只需要判断两样东西即可。一个是OpenID,另一个是Token字符串。用户在手机上用微信账号登陆小程序的时候,会产生一个唯一的OpenID值,商户系统会记录下这个OpenID值。如果商户系统接收到的请求里面没有OpenID值,或者OpenID值跟数据库里面的对不上,就说明这不是一个合法的用户。那么商户系统就不用理会这个请求。仅仅OpenID能核对上还不行,我还要看看发起请求的用户是不是已经登陆了小程序。只有用户登陆小程序之后,才可以证明是本人下单支付,所以商户系统必须要判断用户到底有没有登陆小程序。因为我们搭建的renren-fast后端项目,整合了Shiro和JWT技术,所以成功登陆小程序的用户,renren-fast后端系统都会返回一个Token字符串。小程序每次发起请求的时候都要带上这个令牌字符串,告诉renren-fast后端系统,我现在已经登陆了。当然了,后端系统也要验证这个Token字符串是否有效,以及过没过期。

小程序端核心代码:

uni.getUserInfo({    success:function(resp){        // console.log(resp)        let nickname=resp.userInfo.nickName        let avatarUrl=resp.userInfo.avatarUrl        uni.request({            url:that.url.wx.login,            method:"POST",            data:{                "code":code,                "nickname":nickname,                "photo":avatarUrl            },            success:function(resp){                console.log(resp)                let token=resp.data.token                let expire=resp.data.expire                // uni.setStorageSync("token",token)                // uni.setStorageSync("expire",expire)                uni.switchTab({                    url:"../index/index"                })            }        })    }})

后端Java代码:

application:     wxpay:     app-id: wx4cb8e*********     app-secret: 27b1f2997***************     mch-id: 1526******     key: qv9Kihy***********     cert-path: F:/apiclient_cert.p12
@Data@ApiModel(value = "微信登录表单")public class WxLoginForm {     @ApiModelProperty(value = "临时登陆凭证")     @NotBlank(message="临时登陆凭证不能为空")     private String code;     @ApiModelProperty(value = "昵称")     @NotBlank(message="昵称不能为空")     private String nickname;     @ApiModelProperty(value = "头像URL")     @NotBlank(message="头像URL不能为空")     private String photo;}
 @RestController @RequestMapping("/app/wx") @Api("微信业务接口") public class WxController {     @Value("${application.wxpay.app-id}")     private String appId;     @Value("${application.wxpay.app-secret}")     private String appSecret;     @Value("${application.wxpay.key}")     private String key;     @Value("${application.wxpay.mch-id}")     private String mchId;     @Autowired     private UserService userService;     @Autowired     private OrderService orderService;     @Autowired     private JwtUtils jwtUtils;     @Autowired     private MyWXPayConfig myWXPayConfig;     @PostMapping("login")     @ApiOperation("登录")     public R login(@RequestBody WxLoginForm form) {         //表单校验         ValidatorUtils.validateEntity(form);         String url = "https://api.weixin.qq.com/sns/jscode2session";         HashMap map = new HashMap();         map.put("appid", appId);         map.put("secret", appSecret);         map.put("js_code", form.getCode());         map.put("grant_type", "authorization_code");         String response = HttpUtil.post(url, map);         JSONObject json = JSONUtil.parseObj(response);         String openId = json.getStr("openid");         if (openId == null || openId.length() == 0) {             return R.error("临时登陆凭证错误");         }         UserEntity user = new UserEntity();         user.setOpenId(openId);         QueryWrapper wrapper = new QueryWrapper(user);         int count = userService.count(wrapper);         if (count == 0) {             user.setNickname(form.getNickname());             user.setPhoto(form.getPhoto());             user.setType(2);             user.setCreateTime(new Date());             userService.save(user);         }         user = new UserEntity();         user.setOpenId(openId);         wrapper = new QueryWrapper(user);         user = userService.getOne(wrapper);         long id = user.getUserId();         String token = jwtUtils.generateToken(id);         Map<String, Object> result = new HashMap<>();         result.put("token", token);         result.put("expire", jwtUtils.getExpire());         return R.ok(result);     } }

04

微信创建订单

在用户成功登录,且我们可以获取到用户openid用以和微信服务器交互的时候,我们开始创建一个支付订单,在这里,我省略了添加购物车,优惠券等等电商支付的复杂流程,直接编写一个订单页面,并且在数据库的订单表中,插入一条订单数据;接下来,根据时序图,我们需要将我们的微信订单提交给微信服务器,换来一个微信支付的订单,在进行编码之前,我们首先了解一下生成微信支付订单需要的参数:

在这里,我只展示必须要上传的参数,按照官方文档,在这里可以上传的参数有很多,具体大家可以参考接口文档(https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1)

参数描述:

· appid参数是商户的公众号ID,也就是注册小程序账号时候得到的app-id

· mch_id参数是商户号,申请微信支付的时候,可以在微信商户平台的网站上面得到商户号

· nonce_str参数是随机字符串,这个可以用微信支付的SDK程序来生成

· sign参数是签名字符串,签名字符串,就是我们对上传的数据做MD5计算,得到的结果就是签名字符串了。SDK程序可以帮我们完成这个功能。对上传的数据做签名,一方面预防数据丢包,另外一方面放置数据在网络传输过程中被篡改。所以微信平台收到数据之后,重新计算一下数字签名。跟提交上来的数字签名比较一下就知道了。

· body参数是商品的概要描述,这个内容你可以随便写,是给支付订单做备注的,一般写的都是商品的名称。确认支付之前,小程序会拿着商户的支付参数,去微信平台搜索支付订单,然后用户手机上就会出现这个支付订单的信息了。其中就有订单的概要描述。这就是现在这个参数的用途

· out_trade_no参数是商户订单号,你自己定义,或者用SDK程序生成,都是可以的

· total_fee参数是订单金额,整数类型,单位不是元,而是分

· spbill_create_ip参数是终端的IP地址,这里我们上传Cetus主机的IP地址即可,你写什么IP地址,都不会影响到微信支付的

· notify_url参数是通知地址,这个参数非常重要。因为将来微信支付成功以后,微信平台会把支付成功的结果发送给cetus项目,这里写的就是cetus项目接收支付结果的URL路径。如果这个地址写错了,倒是不会影响到微信支付,只是在支付成功以后,微信平台会每隔几分钟发送一次通知请求。如果没有响应,发过若干次通知之后,微信平台就不会发送通知了。因为cetus项目还没有发布,没有固定网址,所以我们写程序的时候,这个回调地址可以先随便写一个,等将来我们把cetus项目部署在腾讯云上面,再把这个回调通知地址改成真实的网址即可。

· trade_type参数是交易类型,固定写成JSAPI

· openid参数也就是微信用户登陆小程序时候,传给cetus项目的参数

具体的返回参数列表我不在这里详细展示了,接口文档中都有,在返回的参数中,我们最关心的值是prepay_id,也就是微信支付给我们这笔订单生成的微信支付订单号,这个订单号我们需要存入数据库,因为在之后的业务逻辑中还会再次使用到这个参数。

在了解完请求的具体参数列表后,我们开始引入微信支付的SDK,调用微信支付API来完成支付;SDK下载地址:(https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1);

下载之后解压,从com包层级开始,复制到整个项目中;

注意:引入之后,有一个必须要改的点!!!这也是微信支付的一个大坑:

如图所示:我红框标识的地方,在第一次引入的时候,并不是MD5加密,是SHA,这会导致你如果不使用沙箱环境,就会一直报错,一定要修改成MD5。

我们来关注SDK中的核心嘞WXPay.java,在构造方法中我们可以看到:

在这里需要接收一个wxconfig,这个类在SDK中是一个抽象类,我们需要手动实现一个自己的WXPayConfig,并实现抽象方法:

抽象类代码:

创建订单的代码:

@Data@ApiModel(value = "订单付款的表单")public class PayOrderForm {    @ApiModelProperty(value = "订单ID")    @Min(1)    private Integer orderId;}
@Login@PostMapping("/microAppPayOrder")@ApiOperation("小程序付款")public R microAppPayOrder(@RequestBody PayOrderForm form, @RequestHeader HashMap header) {    ValidatorUtils.validateEntity(form);    String token = header.get("token").toString();    Long userId = Long.parseLong(jwtUtils.getClaimByToken(token).getSubject());    int orderId = form.getOrderId();    UserEntity user = new UserEntity();    user.setUserId(userId);    QueryWrapper wrapper = new QueryWrapper(user);    long count = userService.count(wrapper);    if (count == 0) {        return R.error("用户不存在");    }    String openId = userService.getOne(wrapper).getOpenId();    OrderEntity order = new OrderEntity();    order.setUserId(userId.intValue());    order.setId(orderId);    order.setStatus(1);    wrapper = new QueryWrapper(order);    count = orderService.count(wrapper);    if (count == 0) {        return R.error("不是有效的订单");    }    //验证购物券是否有效    //验证团购活动是否有效    order = new OrderEntity();    order.setId(orderId);    wrapper = new QueryWrapper(order);    order = orderService.getOne(wrapper);    //向微信平台发出请求,创建支付订单    String amount = order.getAmount().multiply(new BigDecimal("100")).intValue() + "";    try {        WXPay wxPay = new WXPay(myWXPayConfig);        HashMap map = new HashMap();        map.put("nonce_str", WXPayUtil.generateNonceStr()); //随机字符串        map.put("body", "订单备注");        map.put("out_trade_no", order.getCode());        map.put("total_fee", amount);        map.put("spbill_create_ip", "127.0.0.1");        map.put("notify_url", "https://127.0.0.1/test");        map.put("trade_type", "JSAPI");        map.put("openid", openId);        Map result = wxPay.unifiedOrder(map);        String prepayId = result.get("prepay_id");        System.out.println(prepayId);        if (prepayId != null) {            order.setPrepayId(prepayId);            order.setPaymentType(1);            UpdateWrapper updateWrapper = new UpdateWrapper();            updateWrapper.eq("id", order.getId());            orderService.update(order, updateWrapper);            //生成数字签名            map.clear();            map.put("appId", appId);            String timeStamp = new Date().getTime() + "";            map.put("timeStamp", timeStamp);            String nonceStr = WXPayUtil.generateNonceStr();            map.put("nonceStr", nonceStr);            map.put("package", "prepay_id=" + prepayId);            map.put("signType", "MD5");            String paySign = WXPayUtil.generateSignature(map, key);            return R.ok().put("package", "prepay_id=" + prepayId)                .put("timeStamp", timeStamp)                .put("nonceStr", nonceStr)                .put("paySign", paySign);        }        else {            return R.error("支付订单创建失败");        }    }    catch (Exception e) {        e.printStackTrace();        return R.error("微信支付模块故障");    }}
uni.request({    url: that.url.wx.microAppPayOrder,    method: "POST",    header: {        "token": uni.getStorageSync("token")    },    data: {        "orderId": id    },    success: function(resp) {        // console.log(resp)        let timeStamp = resp.data.timeStamp        let nonceStr = resp.data.nonceStr        let pk = resp.data.package        let paySign = resp.data.paySign    }})

05

最后一步!微信支付

根据时序图,我们已经拿到了微信服务器返回给我们的订单,这时候,我们前端需要让用户确认一下我们的订单,并调起支付:

uni.requestPayment(OBJECT):

uni.requestPayment是一个统一各平台的客户端支付API,不管是在某家小程序还是在App中,客户端均使用本API调用。

本API运行在各端时,会自动转换为各端的原生支付调用API。虽然客户端API统一了,但各平台的支付申请开通、配置回填、服务器开发,仍然需要看各个平台本身的支付文档。比如微信有App支付、小程序支付、H5支付等不同的申请入口和使用流程,对应到uni-app,在App端要申请和使用微信的App支付,而小程序端则申请和使用微信的小程序支付。

到这一步直接上代码:

支付代码:

// 仅作为示例,非真实参数信息。uni.requestPayment({    provider: 'wxpay',    timeStamp: String(Date.now()),    nonceStr: 'A1B2C3D4E5',    package: 'prepay_id=wx20180101abcdefg',    signType: 'MD5',    paySign: '',    success: function (res) {        console.log('success:' + JSON.stringify(res));    },    fail: function (err) {        console.log('fail:' + JSON.stringify(err));    }});
uni.requestPayment({    "timeStamp": timeStamp,    "nonceStr": nonceStr,    "package": pk,    "signType": "MD5",    "paySign": paySign,    success: function() {        uni.showToast({            title: "支付成功"        })    },    fail: function() {        uni.showToast({            title: "支付失败"        })    }})

至此,我们便梳理完微信支付的全部流程,前后台前后端调通,完整实现了微信支付;当然这只是微信支付的第一步,在接下来我们需要继续对订单进行处理;但只要能调起支付!就是长征路的第一步嘛~

悄咪咪的说:很多小伙伴想要调试微信支付,或者小程序的一些企业端功能,苦于没有企业资质,早说嘛~~我有!!我的“菜鸟的大厂梦”就是一个企业资质的小程序呦,如果有这方面需要的小伙伴,可以帮忙推广一下我们的公众号嘛,我可以考虑把你加成我小程序的开发者,可以使用企业资质呦~~~

结语

点击蓝字

关注我们

微信支付 postman_小刘同学微信支付接入全笔记相关推荐

  1. 2022-01-26:最优账单平衡。 一群朋友在度假期间会相互借钱。比如说,小爱同学支付了小新同学的午餐共计 10 美元。如果小明同学支付了小爱同学的出租车钱共计 5 美元。我们可以用一个三元组 (x

    2022-01-26:最优账单平衡. 一群朋友在度假期间会相互借钱.比如说,小爱同学支付了小新同学的午餐共计 10 美元.如果小明同学支付了小爱同学的出租车钱共计 5 美元.我们可以用一个三元组 (x ...

  2. 微信公众平台、微信公众平台.小程序、微信.开放平台三者关系及unionid

    以下内容,仅限于根据自己开发以及阅读微信文档总结,错误之处敬请指出,共同进步! 一.微信公众平台.微信公众平台.小程序.微信.开放平台登录地址 项目 微信公众平台 微信公众平台.小程序 微信.开放平台 ...

  3. 小何同学的leetcode刷题笔记 基础篇(01)整数反转

    小何同学的leetcode刷题笔记 基础篇(01)整数反转[07] *** [01]数学取余法*** 对数字进行数位操作时,常见的方法便是用取余的方法提取出各位数字,再进行操作 操作(1):对10取余 ...

  4. ios11修改微信步数_小程序同步微信步数

    文章正文 小程序获取微信运动步数,咱们看文档:https://developers.weixin.qq.com/miniprogram/dev/api/open-api/werun/wx.getWeR ...

  5. 分享一款微信多开小工具:微信多开助手PC版

    网上找了很多微信多开方法,发现要么不起作用,要么用起了特别繁琐,干脆自己做一个,只要开着这个小工具,微信就可以无限多开,操作简单!先来个特写: 下面是多开效果! 微信多开小工具,大小不到1M,下载地址 ...

  6. 微信开发调试小工具进化→微信用户发送信息模拟器发布!——这标题起真是好数码暴龙的说...

    直接说正题吧.最近在做微信第三方服务器.在模拟用户发过来的请求方面,网上流传着一个"微信开发调试小工具",谁做都不知道了.反正我觉得那个东西看起来挺不爽的,因为其信息发送类型不是很 ...

  7. ReactNative和微信应用号小程序之间的分析异同全平台开发

    最近2天,互联网圈和技术圈的热点话题非应用号莫属.有些文章从产品角度深入探讨哪些类型的服务适合做成应用号,也有直接发布内应用号的开发教程的.做为腾讯云的技术布道师技术开发人员,我想换个不一样的角度,从 ...

  8. 小刘同学的第七十四篇博文

    近期主要还是为论文服务的,自己这边虽然有一个老师写的项目架子,但今天一个技术很牛逼的学弟帮我看了下,说这个老师搭的架子很烂,基本上也就是能用而已. 5月7号以后,随时答辩,按往年的惯例,也大概就是10 ...

  9. 小刘同学的第八十篇博文

    校招陆陆续续开始了,但是自己却还没准备好啊. 想不到已经八十天了,从去年注册博客园开始,到现在已经过去80个日夜了,这八十个日夜到底是有多少时间花在技术上的,自己心里清楚. 事情实在太多太多: 1.[ ...

最新文章

  1. 微软华人团队刷新COCO记录!全新目标检测机制达到SOTA|CVPR 2021
  2. 4412 GPIO读 和 ioremap控制GPIO寄存器
  3. ComputeShader中Counter类型的使用
  4. ThreadPoolExecutor的一点理解 专题
  5. 离2006年考研还有一个月
  6. keras cnn注意力机制_从发展历史视角解析Transformer:从全连接CNN到Transformer
  7. pat天梯赛L2-025. 分而治之
  8. 命运(HDU-2571)
  9. Keepalived-在没有之前配置的Nginx高可用(不建议采纳)
  10. Google Chrome不支持ClickOnce部署
  11. 多光谱、高光谱与雷达数据等产品级划分标准
  12. 笨方法学python 习题31
  13. 光伏产品标准 - IEC 61215:2021版系列简介及标准下载
  14. 005:列表、循环、分支练习题
  15. Sumproduct函数的使用方法
  16. 程序员常常看到的英文
  17. 企名片-企业数据js加密破解
  18. vue uniapp实现分段器效果
  19. 20164324王启元 Exp4恶意代码分析
  20. mysql显示表的所有列车_MySQL命令行查看表信息 | 夕辞

热门文章

  1. Matlab拟合幂律分布
  2. left join-on-and 与 left join-on-where
  3. dsf5.0没登录显示登录弹框
  4. python win32com excel_如何用Python win32com处理Excel数据?
  5. 仪器科学与技术毕业论文范文
  6. Assignment | 05-week3 -Part_2-Trigger Word Detection
  7. Dual Encoding for Video Retrieval by T ext
  8. Biotin-PEG-SH生物素-聚乙二醇-巯基结构式;SH-PEG-Biotin
  9. 用于CTF(MISC)的kali虚拟机更改过程记录
  10. 华为已注册商标鸿蒙,华为已注册华为鸿蒙商标:整本山海经都被华为注册了