博客 (27)

  1. 微信的 H5 页面前端引导到:

    https://api.magcloud.cc/magshare/{siteId}?jump_url={jump_url}&content_url={content_url}

    各参数含义见官方文档:http://doc.magcloud.net/325339

    content_url 地址(一般仍为活动页面)接收参数 magshareredirect,值为 1 则跳转到自定义的引导页。

  2. 自定义引导页按 iOS 和安卓显示图文和箭头,引导用户点击右上角菜单,在浏览器中打开。

  3. 在浏览器 H5 中引导打开:

    {siteId}://pagejump?jump_url={jump_url}

    各参数含义见第 1 步中的文档。

    这里涉及一个“若未安装则引导安装”的过程,可以选择下方合适的一种方式实现:

    • A 标签的 href 填入下载地址,onclick 事件 location 到 App

    • onclick 事件 location 到 App,再用 setTimeOut location 到下载地址

    • 若仍无法实现预期效果,可以在弹出层中放置“未安装,前往下载”和“已安装,立即打开”两个按钮,让用户自己选择

  4. 若下载地址为应用宝,那么在 iOS 中可能会出现“打开 App 后继而自动打开 App Store”的情况,可以自建中转的 App 介绍页引导到 App Store 或应用宝。

  5. 分别实现微信和 MAGAPP 客户端的分享

xoyozo 5 年前
5,041

当微信支付的 notify_url 填写的是 https 的回调地址时,如果遇到支付成功但没有接收到回调的情况,可能是服务器开启了 SNI。

SNI,即服务器名称指示,用于在一台服务器上支持多个网站使用不同的 SSL 证书。

解决方案:

  • 方法一、notify_url 改为 http 的回调地址,缺点是容易被运营商 QJ,而且如果日后网站开启强制要求 https 访问就会使回调失败;

  • 方法二、【不推荐】可以为该网站单独配置一台服务,不使用 SNI,缺点是增加成本;

  • 方法三、【推荐】该网站仍然“需要服务器名称指示”,在 IIS 中给默认网站(不是当前网站)添加一个 https 的域名绑定,不勾选“需要服务器名称指示”,证书随便选一个。缺点是时间一长容易忘记有这回事,不要轻易删除该绑定就好了,而且在网站搬迁的时候也同样要做该配置。

image.png

感谢 V2EX

xoyozo 6 年前
7,658

微信支付商户平台注册时填写的企业法人为超级管理员,此处对比的是在“员工账号管理”中添加的“管理员”身份的账号,并且“配置权限”中勾选了所有权限。


超级管理员员工管理员
接收在“下载证书”、“设置操作密码”等操作中下发的短信验证码
开发配置(配置支付授权目录、配置扫码回调链接)
AppID 授权管理
随时补充


xoyozo 6 年前
19,626

本文讲述使用网页开发适用于宴会、活动、年会现场 LED 大屏幕显示抽奖、抢楼、游戏、视频等互动界面,通过在浏览器中打开后 F11 全屏投影到大银幕上。


设计思路:

  1. 为了保证最佳的显示效果,设计稿的尺寸按大屏幕的“宽高比”来做(本文以4米*3米的大银幕为例,设计稿以800*600像素为例)。

  2. 由于电脑的视频输出分辨率很有可能与大银幕的宽高比不同,那么我们全屏投影后会界面会有变形,但是投影设备接收到电脑视频信号后再全屏显示到大银幕时,界面又会变回正常。

  3. 按设计稿的尺寸开发网页,并使用 CSS3 的 scale 实现整体伸缩,目的是自适应窗口尺寸来全屏显示内容,无留白、无裁切。整体拉伸的要求是文字、图片、背景、间距、iframe 等所有元素以统一的比例拉伸,无错位。

  4. 网页上通过 JS 设置一些快捷键来实现页面展示内容的切换。

  5. 若投影设备功能有限或操作员水平有限等原因导致无法顺利设置输入输出分辨率时,网页可设置 x 轴和 y 轴两个方向的伸缩系数、定位偏移量等,更灵活地实现完美投影需求。


开发要点:

  • 网页尺寸按设计稿来定(以 800x600 为例)。

  • 使用 CSS3 的 transform 的 scale 来实现缩放,以电脑显示器分辨率 1920x1080,宽放大 1920/800 倍,高放大 1080/600 倍,即可将设计稿全屏展示到电脑显示器上。为了更灵活,我们使用窗口大小来代替显示器分辨率,这样不管怎么改变窗口大小或者全屏,设计稿都能完整地显示在窗口上。

  • 在页面初始化和窗口尺寸变化时重新调整设置上述比例。


代码参考:

xoyozo 6 年前
8,965

在 Godday 上搜索域名,会引导购买一个叫“域名购买服务”的产品(英文版上是人民币 472 元,新加坡简体中文版上的新加坡元换算成人民币更贵),稍不注意就会以为是域名本身,其实是让 Godday 联系域名所有者实现交易的服务费。付款后可设置你希望购买该域名的价格范围。

发现入坑后立刻问度娘,发现好多人跟我一样被坑。

于是按 https://sg.godaddy.com/zh/contact-us 上的 400 电话打过去,客服 MM 说原则上是不退款的哦。后来又得知我没有设置过域名报价,她说可以试试提交一下退款申请。听了十几分钟的音乐后,客服提交完毕,告诉我,如果审批成功,2 天内会收到邮件,15 天内会收到退款。

等了 48 小时没有收到邮件。第 3 天晚上,直接收到退款。

xoyozo 6 年前
4,035
二维码类型可扫码授权的微信号
其它微信号
直接扫码将二维码截图发送给可扫码授权的微信号无需劳烦管理员的方式
登录微信公众平台管理员与运营者

扫码后需管理员验证登录(管理员通过“公众平台安全助手”接收消息,点开详情后点击“确定”)

授权人用摄像头扫码,点击“确定”
成为运营者
设置公众号IP白名单管理员与长期运营者无法完成授权人用摄像头扫码,点击“确定”
成为长期运营者
公众号绑定运营者管理员无法完成管理员用摄像头扫码,点击“确定”

小程序添加/删除项目成员

管理员无法完成管理员直接识别二维码,点击“确定”
小程序修改名称、修改简称、修改登录邮箱、修改密码管理员无法完成“修改简称”管理员用摄像头扫码,点击“确定”,其它未测
小程序修改业务域名
管理员、有“开发者”权限的项目成员无法完成授权人直接识别二维码,点击“确定”成为有“开发者”权限的项目成员
微信开放平台绑定小程序管理员无法完成管理员用摄像头扫码,点击“确定”

① 摄像头扫码:无法直接用“识别二维码”,必须把二维码发送到身边的其它设备,再使用手机摄像头完成扫码

② 成为运营者要求绑定银行卡;成为运营者将占用“微信号绑定的帐号”数量,详见说明。若达到上限,只有法定代表人可继续成为运营者。在公众号“公众平台安全助手”中可查询绑定的帐号。

另外:

  • 小程序添加/删除体验成员无须管理员授权

  • 如果管理员未关注公众号“公众平台安全助手”,待测……

xoyozo 6 年前
4,762

在学习和使用 ASP.NET Web API 之前,最好先对 RESTful 有所了解。它是一种软件架构风格、设计风格,而不是标准。


推荐视频教程:https://www.imooc.com/learn/811

讲师:会飞的鱼Xia

需时:2小时25分


REST API 测试工具:

在 Chrome 网上应用店中搜索:Restlet Client

或网站 https://client.restlet.com


>>> 资源路径

SCHEME :// HOST [ ":" PORT ] [ PATH [ "?" QUERY ]]

其中 PATH 中的资源名称应该使用复数名词,举例:

GET https://xoyozo.net/api/Articles/{id}

POST https://xoyozo.net/api/Articles


>>> HTTP verb(对应 CURD 操作):

方法功能ASP.NET Web API 接口返回类型(一般的)
GET取一个或多个资源T 或 IEnumerable<T>
POST增加一个资源T
PUT修改一个资源T
DELETE删除一个资源void
PATCH更新一个资源(资源的部分属性)
HEAD获取报头
OPTIONS查询服务器性能或资源相关选项和需求


>>> 过滤信息:

如分页、搜索等


>>> 常用状态码:

200
OK
指示请求已成功
201
Created
资源创建成功(常见用例是一个 PUT 请求的结果)
202
Accepted
该请求已被接收但尚未起作用。它是非承诺的,这意味着HTTP中没有办法稍后发送指示处理请求结果的异步响应。
204
No Content
成功 PUT(修改)或 DELETE(删除)一个资源后返回空内容(ASP.NET Web API 中将接口返回类型设置为 void 即可)
400
Bad Request
服务器无法理解请求(如必填项未填、内容超长、分页大小超限等),幂等
401
Unauthorized
未授权(如用户未登录时执行了须要登录才能执行的操作),通俗地讲,是服务器告诉你,“你没有通过身份验证 - 根本没有进行身份验证或验证不正确 - 但请重新进行身份验证并再试一次。”这是暂时的,服务器要求你在获得授权后再试一次。
403
Forbidden
服务器理解请求但拒绝授权(是永久禁止的并且与应用程序逻辑相关联(如不正确的密码、尝试删除其它用户发表的文章)、IP 被禁止等),通俗地讲,是服务器告诉你,“我很抱歉。我知道你是谁 - 我相信你说你是谁 - 但你只是没有权限访问此资源。”
404
Not Found
找不到请求的资源(指资源不存在,如果找到但无权访问则应返回 403)
405
Method Not Allowed
请求方法被禁用(HTTP 动词)
500
Internal Server Error
服务器遇到阻止它履行请求的意外情况(如数据库连接失败)
503
Service Unavailable
服务器尚未准备好处理请求(常见原因是服务器因维护或重载而停机,这是临时的,可用于在单线程处理事务时遇到被锁定时的响应,如抽奖活动、抢楼活动,防止因并发导致逻辑错误)


>> 错误处理:

C# 例:throw new HttpResponseException(HttpStatusCode.NotFound);

PHP 例:throw new Exception('文章不存在', 404);


>>> 返回结果:

JSON/XML

不要返回密码等机密字段


>>> 其它:

身份验证窗口(浏览器弹窗,类似 alert,非页面表单),明文传输,安全性不高,不建议使用。实现:

在 Headers 中添加 Authorization:Basic “用户名:密码”的 Base64 编码


xoyozo 6 年前
4,019

本文不定时更新中……

收集了一些在开发过程中遇到的一些问题的解决方法,适合新手。


异常:

出现脚本错误或者未正确调用 Page()

原因:不小心删了第一行内容:<template>


异常:

模块编译失败:TypeError: Cannot read property 'for' of undefined

at fixDefaultIterator (D:\HBuilderX\plugins\uniapp\lib\mpvue-template-compiler\build.js:4277:24)

at mark (D:\HBuilderX\plugins\uniapp\lib\mpvue-template-compiler\build.js:4306:5)

at markComponent (D:\HBuilderX\plugins\uniapp\lib\mpvue-template-compiler\build.js:4371:5)

at baseCompile (D:\HBuilderX\plugins\uniapp\lib\mpvue-template-compiler\build.js:4384:15)

at compile (D:\HBuilderX\plugins\uniapp\lib\mpvue-template-compiler\build.js:4089:28)

at Object.module.exports (D:\HBuilderX\plugins\uniapp\lib\mpvue-loader\lib\template-compiler\index.js:43:18)

原因:新建的页面(简单模板)只有以下 3 个标签,须在 <template /> 中添加一些代码,如 <view />

<template>
</template>

<script>
</script>

<style>
</style>

异常:

模块编译失败:TypeError: Cannot read property 'toString' of undefined

at Object.preprocess (D:\HBuilderX\plugins\uniapp\lib\preprocess\lib\preprocess.js:56:15)

at Object.module.exports (D:\HBuilderX\plugins\uniapp\lib\preprocessor-loader.js:9:25)

原因:没有原因,纯抽风,HX 关掉再开就好了。


异常:

Cannot set property 'xxx' of undefined;at pages/... onLoad function;at api request success callback function

原因:属性未定义,例如 

data() {
	return {
		item: { }
	}
}

而直接赋值 this.item.abc.xxx = '123';

解决:

data() {
	return {
		item: {
			abc: ''
		}
	}
}

问:page 页面怎样修改 tabBar?

答:官方文档未给出答案,百度了一圈也无果(2018-10-23),但有人说小程序的 setTabBarBadge() 方法设置角标是可以用的。


坑:

VM1694:1 获取 wx.getUserInfo 接口后续将不再出现授权弹窗,请注意升级

参考文档: https://developers.weixin.qq.com/community/develop/doc/0000a26e1aca6012e896a517556c01

填坑:放弃使用 uni.getUserInfo 接口来获取用户信息,uni.login 可返回用于换取 openid / unionid 的 code,参:uni.login、 code2Session


坑:字符搜索(当前目录)(Ctrl+Alt+F)搜不出所有结果

填坑:顾名思义他只搜索当前目录,即当前打开文件所在目录,而非我误认为的整个项目根目录。在“项目管理器”中选中要搜索字符的目录即可。


坑:uni.navigateTo() 或 uni.redirectTo() 没反应

填坑:这两个方法不允许跳转到 tabbar 页面,用 uni.switchTab() 代替。


坑:使用“Ctrl+/”快捷键弹出“QQ五笔小字典”窗口

解决:打开QQ五笔“属性设置”,切换到“快捷键设置”选项卡,把“五笔小字典”前的勾取消(即使该组合键是设置为Ctrl+?)。


坑:<rich-text /> 中的 <img /> 太大,超出屏幕宽度

填坑:用正则表达式给 <img /> 加上最大宽度

data.data.Content = data.data.Content.replace(/\<img/gi, '<img style="max-width:100%;height:auto" ');

坑:无法重命名或删除目录或文件

填坑一:“以管理员身份运行”HBuilder X 后再试。

填坑二:关闭微信开发者工具、各种手机和模拟器后再试。

填坑三:打开“任务管理器”,结束所有“node.exe”进程后再试。


坑:

 thirdScriptError 
 sdk uncaught third Error 
 (intermediate value).$mount is not a function 
 TypeError: (intermediate value).$mount is not a function
Page[pages/xxxx/xxxx] not found. May be caused by: 1. Forgot to add page route in app.json. 2. Invoking Page() in async task.
Page is not constructed because it is not found.

填坑:关闭微信开发者工具、各种手机和模拟器后,删除“unpackage”目录。


坑:

Unexpected end of JSON input;at "pages/news/view" page lifeCycleMethod onLoad function
SyntaxError: Unexpected end of JSON input

填坑:给 uni.navigateTo() 的 url 传参时,如果简单地将对象序列化 JSON.stringify(item),那么如果内容中包含“=”等 url 特殊字符,就会发生在接收页面 onLoad() 中无法获取到完整的 json 对象,发生异常。

uni.navigateTo({
	url: "../news/view?item=" + JSON.stringify(item)
})

所以应该把参数值编码:

uni.navigateTo({
	url: "../news/view?item=" + escape(JSON.stringify(item))
})

如果是一般的 web 服务器来接收,那么会自动对参数进行解码,但 uni-app 不会,如果直接使用:

onLoad(e) {
	this.item = JSON.parse(e.item);
}

会发生异常:

Unexpected token % in JSON at position 0;at "pages/news/view" page lifeCycleMethod onLoad function
SyntaxError: Unexpected token % in JSON at position 0

需要解码一次:

onLoad(e) {
	this.item = JSON.parse(unescape(e.item));
}

需要注意的是,unescape(undefined) 会变成 'undefined',如果要判断是否 undefined,应是 unescape 之前。


坑:图片变形

填坑:mode="widthFix"


坑:页面如何向 tabBar 传参

填坑:全局或缓存


坑:编译为 H5 后,出现:Access-Control-Allow-Origin

填坑:参阅


坑:编译为 H5 后,GET 请求的 URL 中出现“?&”

填坑 :客户端只求 DCloud 官方能够尽快修复这个 bug,IIS 端可以暂时用 URL 重写来防止报 400 错误,参此文


坑:[system] errorHandler TypeError: Cannot read property 'forEach' of undefined

填坑:待填


xoyozo 6 年前
17,920

制作类似下图中的拖拽排序功能:

image.png

1. 首先数据库该表中添加字段 sort,类型为 double(MySQL 中为 double(0, 0))。

2. 页面输出绑定数据(以 ASP.NET MVC 控制器为例):

public ActionResult EditSort()
{
    if (!zConsole.Logined) { return RedirectToAction("", "SignIn", new { redirect = Request.Url.OriginalString }); }

    db_auto2018Entities db = new db_auto2018Entities();

    return View(db.dt_dealer.Where(c => c.enabled).OrderBy(c => c.sort));
}

这里可以加条件列出,即示例中 enabled == true 的数据。

3. 前台页面引用 jQuery 和 jQuery UI。

4. 使用 <ul /> 列出数据:

<ul id="sortable" class="list-group gutter list-group-lg list-group-sp">
    @foreach (var d in Model)
    {
        <li class="list-group-item" draggable="true" data-id="@d.id">
            <span class="pull-left"><i class="fa fa-sort text-muted fa m-r-sm"></i> </span>
            <div class="clear">
                【id=@d.id】@d.name_full
            </div>
        </li>
    }
</ul>

5. 初始化 sortable,当拖拽结束时保存次序:

<script>
    var url_SaveSort = '@Url.Action("SaveSort")';
</script>
<script>
    $("#sortable").sortable({
        stop: function (event, ui) {
            // console.log('index: ' + $(ui.item).index())
            // console.log('id: ' + $(ui.item).data('id'))
            // console.log('prev_id: ' + $(ui.item).prev().data('id'))
            $.post(url_SaveSort, {
                id: $(ui.item).data('id'),
                prev_id: $(ui.item).prev().data('id')
            }, function (json) {
                if (json.result.success) {
                    // window.location.reload();
                } else {
                    toastr["error"](json.result.info);
                }
            }, 'json');
        }
    });
    $("#sortable").disableSelection();
</script>

这里回传到服务端的参数为:当前项的 id 值、拖拽后其前面一项的 prev_id 值(若移至首项则 prev_id 为 undefined)。

不使用 $(ui.item).index() 是因为,在有筛选条件的结果集中排序时,使用该索引值配合 LINQ 的 .Skip 会引起取值错误。

6. 控制器接收并保存至数据库:

[HttpPost]
public ActionResult SaveSort(int id, int? prev_id)
{
    if (!zConsole.Logined)
    {
        return Json(new { result = new { success = false, msg = "请登录后重试!" } }, JsonRequestBehavior.AllowGet);
    }

    db_auto2018Entities db = new db_auto2018Entities();

    dt_dealer d = db.dt_dealer.Find(id);

    // 拖拽后其前项 sort 值(若无则 null)(此处不需要加 enabled 等筛选条件)
    double? prev_sort = prev_id.HasValue
        ? db.dt_dealer.Where(c => c.id == prev_id).Select(c => c.sort).Single()
        : null as double?;

    // 拖拽后其后项 sort 值(若无前项则取首项作为后项)(必须强制转化为 double?,否则无后项时会返回 0,导致逻辑错误)
    double? next_sort = prev_id.HasValue
        ? db.dt_dealer.Where(c => c.sort > prev_sort && c.id != id).OrderBy(c => c.sort).Select(c => (double?)c.sort).FirstOrDefault()
        : db.dt_dealer.Where(c => c.id != id).OrderBy(c => c.sort).Select(c => (double?)c.sort).FirstOrDefault();

    if (prev_sort.HasValue && next_sort.HasValue)
    {
        d.sort = (prev_sort.Value + next_sort.Value) / 2;
    }
    if (prev_sort == null && next_sort.HasValue)
    {
        d.sort = next_sort.Value - 1;
    }
    if (prev_sort.HasValue && next_sort == null)
    {
        d.sort = prev_sort.Value + 1;
    }

    db.SaveChanges();

    return Json(new { item = new { id = d.id }, result = new { success = true } });
}

需要注意的是,当往数据库添加新项时,必须将 sort 值设置为已存在的最大 sort 值 +1 或最小 sort 值 -1。

var d = new dt_dealer
{
    name_full = "新建项",
    sort = (db.dt_dealer.Max(c => (double?)c.sort) ?? 0) + 1,
};


xoyozo 6 年前
6,486

UEditor 的 ASP 版本在虚拟空间上传文件失败,提示“上传错误”或“上传失败,请重试”等,是因为其文件上传组件在创建目录时,没有网站目录外的访问权限。


例如上传文件的磁盘保存路径为:

D:\web\sitename\wwwroot\upload\20180523\123.jpg


百度编辑器的上传组件会依次判断以下目录是否存在,不存在则创建:

D:\

D:\web\

D:\web\sitename\

D:\web\sitename\wwwroot\

D:\web\sitename\wwwroot\upload\

D:\web\sitename\wwwroot\upload\20180523\


虚拟空间自动配置的网站根目录可能是:

D:\web\sitename\wwwroot\


所以,判断存在或创建以下目录没有问题:

D:\web\sitename\wwwroot\upload\

D:\web\sitename\wwwroot\upload\20180523\


但判断存在以下目录时会因为权限不足而失败:

D:\

D:\web\

D:\web\sitename\


解决方法是找到文件 asp/Uploader.Class.asp

找到 CheckOrCreatePath 这个 Function,替换为:

Private Function CheckOrCreatePath( ByVal path )    
   Set fs = Server.CreateObject("Scripting.FileSystemObject")    
   Dim parts    
   Dim root : root = Server.mappath("/") & "\"    
   parts = Split( Replace(path, root, ""), "\" )    
   path = root    
   For Each part in parts    
       path = path + part + "\"    
       If fs.FolderExists( path ) = False Then    
           fs.CreateFolder( path )    
       End If    
   Next    
End Function


另外,如果服务端无法通过 Request.Form 来接收该值,把 <form /> 放到 <table /> 外面试试。

或者绑定 contentChange 事件来赋值(不推荐):

ue.addListener("contentChange", function() {
    document.getElementsByName('xxxxxx')[0].value = ue.getContent();
});

此方法的缺点是,不是所有操作都能触发 contentChange 事件,比如剪切、上传图片等。

改进方法(推荐):在 form 提交之前赋值。

xoyozo 7 年前
4,825