且听疯吟如此生活三十年

假设我们要实现一个炉石的对战机制,要怎么做呢?
并没有游戏开发经验,所以以下都是乱猜加瞎扯~

分类

首先,我们把炉石里的主要元素分成几类:

  • 法术
    法术很好理解,一般来说法术包括几个要素

    • 目标
      • 可能是一个目标也可能是多个目标
      • 可能包含一个筛选,比如「对非恶魔随从造成伤害」
    • 效果
      • 可能是给目标一个 buff 或者 debuff,也可能是造成伤害等等
      • 大部分法术都是立即效果
      • 也有一部分法术是触发效果,比如 奥秘,比如 本回合随从生命值不会降低到 1 点以下
  • 武器

  • 英雄

    • 英雄技能可以看作一个法术
  • 随从

    • 随从本身具有的属性

      • 花费/攻击/血量
      • 类别: 恶魔/鱼人/野兽
      • 其他
        我们以 https://hearthstonejson.com/ 上的卡牌火车王的 Json 数据为例:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      {
      "id": "EX1_116",
      "name": "Leeroy Jenkins",
      "text": "<b>Charge</b>. <b>Battlecry:</b> Summon two 1/1 Whelps for your opponent.",
      "rarity": "LEGENDARY",
      "type": "MINION",
      "cost": 5,
      "attack": 6,
      "health": 2,
      "collectible": true,
      "set": "EXPERT1",
      "faction": "ALLIANCE",
      "artist": "Gabe from Penny Arcade",
      "flavor": "At least he has Angry Chicken.",
      "mechanics": [
      "BATTLECRY",
      "CHARGE"
      ],
      "dust": [
      1600,
      3200,
      400,
      1600
      ]
      }
    • 随从效果

      • 即上面的 mechanics 数据
      • 目前炉石包括了 冲锋、亡语、发现、战吼、法术伤害、连击 等等大概二十多种效果

效果

  • 法术效果
    • 法术效果相对来说容易归纳
  • 随从效果
    • 实际上随从效果可以认为是法术效果,比如自带亡语的随从,可以认为是通过法术给予了亡语效果——毕竟,炉石里面确实存在着这样的法术
    • 具有亡语效果 随机召唤一个法力消耗为 3 的随从 的随从死亡时,可以认为是使用了一个 随机召唤一个法力消耗为 3 的随从 的法术
    • 光环是一种特殊的法术效果,拥有更加复杂的触发条件,比如 将你的治疗法术或技能改为造成等量的伤害

对局

  • 从对局中我们可以总结出一些规则,比如大家经常会讨论的 结算顺序
    • 虽然打出一张牌表面上看起来很简单,但是背后其实包括了多个阶段,比如:
      • 选择战吼目标并出牌,此时扣费
      • 登场效果触发,这个时候一部分其他卡牌的效果已经会被触发,比如 任务达人
      • 召唤效果触发,比如会触发 送葬者
      • 战吼效果触发,此时开始结算战吼效果
      • 奥秘效果触发,这也是为什么 谢娜 这张牌可以先偷到复制但是自己本身并不会被复制
      • 召唤结束,这时会触发 飞刀杂耍者 之类的效果
        (结算比较复杂,而且涉及到各种死亡结算和效果,而且暴雪也没有出官方的结算规则,所以,以上只是举个栗子~
  • 从上面的例子可以看到,不同的牌在不同的阶段触发效果
    假设我们需要实现一张 飞刀杂耍者,每召唤一个随从就发射一把飞刀
    • 我们可以很容易分开两个角色
      • 系统,它担任裁判的角色
      • 卡牌
        这样由系统去执行整个流程,在召唤了一个随从后,通知 飞刀杂耍者 去执行发射飞刀的任务
  • 显然我们没办法把所有卡牌逻辑都塞在 系统 这个角色中,假设我们有 500 张卡牌,那么 系统 这部分的调用代码可能会突破天际
  • 我们也不能让 飞刀杂耍者 自己去监控整场对局,从而决定什么时候发射飞刀,显然这样 系统 这个角色就失去了其意义,代码也会无比混乱
  • 实际上对于这样的问题,我们有一个经典的例子(或许你在面试中已经被问了无数次的 「猫叫老鼠跑人醒」)
    • 这就是观察者模式(事件-通知)
    • 我们只需要让 飞刀杂耍者系统 那里注册一个事件,告诉它,有随从被召唤的时候,通知我,然后由我来执行对应的逻辑
    • 系统 则需要维护一个注册的事件列表,比如随从被召唤的事件、随从死亡的事件等等
    • 系统 按结算规则来执行,并在对应的事件触发的时候,按一定的顺序去通知即可

其他

  • 炉石包含很多不同的效果,事实上这也正是它好玩的地方,但一来暴雪没有出明确的结算规则和效果解释,有些卡牌的效果也找不到同类和规律(比如改变战吼效果的 铜须 光环,和改变法术执行目标的 扰咒术 等等),所以有些时候想总结一套规则来套用所有卡牌不太容易
  • 对于单个的特殊的效果,hardcode 也是一个解决办法

插件

额外插一点关于炉石插件的内容,之前看到有童鞋在好奇

  • 炉石传说有一个隐藏的 debug-logging 模式,打开之后会产生非常详细的记录,通过解析这个 log 可以做到记录和获取对局信息
  • 网易的盒子貌似额外使用了抓包解析的方法,所以功能会强大一些,比如导入导出卡组的功能
  • 但是考虑到可能触犯 ToS ,其他插件没有这么做
  • log 大概是这样的:
    1
    [Zone] ZoneChangeList.ProcessChanges() - id=1 local=False [name=Garrosh Hellscream id=4 zone=PLAY zonePos=0 cardId=HERO_01 player=1] zone from -> FRIENDLY PLAY (Hero)
    • 可以看出 log 是非常详细的,包含各种事件的触发
    • 解析完整的 log 可以帮助理解整个炉石的事件机制
  • 当初想要自己实现盒子的时候参考过这篇文章 https://www.reddit.com/r/hearthstone/comments/268fkk/simple_hearthstone_logging_see_your_complete_play
  • 当然现在很多开源的插件了,比如 https://github.com/HearthSim/Hearthstone-Deck-Tracker

待补充

想到哪写到哪,有什么想法再补充吧 =-=

从《凯恩之书》开始就期待了,终于出了编年史
不负责任的猜想是暴雪觉得魔兽世界这个剧情坑太大太多懒得圆吧
还好终于出了

中亚还是挺快的,原来预计发货时间排到 6 月,都想去美亚订了
幸好还是提前发货了

大部分是为信仰充值吧
不过这次编年史的内容还是挺有趣的
有很多剧情上的填坑和重新解读
当然也少不了暴雪粑粑的吃书

比预计的要薄一点,但还是挺有分量的
这只是 Volume Ⅰ,估计后续还会有几本(SHUT UP AND TAKE MY MONEY!

印刷质量还行,但也没有到惊艳的程度。
意外的发现 Printed in China =-=

英文看得还是有点费力……
慢慢啃吧,有什么好玩的内容会更新在这里(大概吧~



###For the Horde!

编译、测试、打包、部署这一系列的操作实在是太麻烦而且容易出错漏,能自动化的东西我们就没必要手动去点。端着咖啡悠哉地等着叮的一声,安装包出现在面前,这才是我们想要的
花了点时间,把正在进行的 React Native 项目的自动部署完善了一下,实现了通过 Travis CI 自动编译测试,并打包成 ipa 发布到 FTP 的整个流程
现在只要 Push 到 Github 上,等到 Travis CI 运行完成,就直接可以拿到 ipa 包安装测试了

准备工作:
首先你需要一个 Github 账户
Travis CI 连接 Gtihub 后,会自动检查根目录下带有 .travis.yml 的项目
关于 Travis CI 的功能和文档,请参考 https://docs.travis-ci.com/

这里给出一个 React Native 项目的 yml 文件示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
language: objective-c
osx_image: xcode7.1
xcode_project: ios/MyApp.xcodeproj
xcode_scheme: MyApp
env:
matrix:
- SPEC=spec1

before_install:
- ./scripts/decrypt_key.sh
- ./scripts/add_key.sh
- brew update

install:
- brew reinstall node flow watchman xctool
- npm install -g react-native-cli

branches:
only:
- master

script:
- ./scripts/release.sh

首先设置项目编译环境:

包括 language 和 要使用的编译镜像 osx_image,并指定项目文件和编译的 scheme

证书加密:

由于 iOS 打包过程需要一些证书密钥,这些是无法公开 Push 到 Github 的
虽然 Travis CI 没有提供 security file 的功能,但是提供了一个 security 的环境变量的功能
因此,我们可以通过在本地加密证书然后上传,线上编译时再解开来实现这一目的

比如:

  • Encrypt
1
2
3
4
# encrypt_key.sh
openssl aes-256-cbc -k ${ENCRYPT_PASS} -in scripts/MyAppdev.mobileprovision -out scripts/MyAppdev.mobileprovision.enc -a
openssl aes-256-cbc -k ${ENCRYPT_PASS} -in scripts/dist.cer -out scripts/dist.cer.enc -a
openssl aes-256-cbc -k ${ENCRYPT_PASS} -in scripts/dist.p12 -out scripts/dist.p12.enc -a
  • Decrypt
1
2
3
4
# decrypt_key.sh
openssl aes-256-cbc -k ${ENCRYPT_PASS} -in scripts/MyAppdev.mobileprovision.enc -out scripts/MyAppdev.mobileprovision -d -a
openssl aes-256-cbc -k ${ENCRYPT_PASS} -in scripts/dist.cer.enc -out scripts/dist.cer -d -a
openssl aes-256-cbc -k ${ENCRYPT_PASS} -in scripts/dist.p12.enc -out scripts/dist.p12 -d -a

注意: 请不要直接上传你的证书和密钥!也不要把密码上传到公开的地方!

导入证书到 Travis 编译环境:

参考下面的步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# add_key.sh

# Create a custom keychain
security create-keychain -p travis ios-build.keychain

# Make the custom keychain default, so xcodebuild will use it for signing
security default-keychain -s ios-build.keychain

# Unlock the keychain
security unlock-keychain -p travis ios-build.keychain

# Set keychain timeout to 1 hour for long builds
# see http://www.egeek.me/2013/02/23/jenkins-and-xcode-user-interaction-is-not-allowed/
security set-keychain-settings -t 3600 -l ~/Library/Keychains/ios-build.keychain

# Add certificates to keychain and allow codesign to access them
security import ./scripts/apple.cer -k ~/Library/Keychains/ios-build.keychain -T /usr/bin/codesign
security import ./scripts/dist.cer -k ~/Library/Keychains/ios-build.keychain -T /usr/bin/codesign
security import ./scripts/dist.p12 -k ~/Library/Keychains/ios-build.keychain -P $KEY_PASS -T /usr/bin/codesign


# Put the provisioning profile in place
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp "./scripts/MyAppdev.mobileprovision" ~/Library/MobileDevice/Provisioning\ Profiles/

安装环境

安装编译所需要的环境,比如 xctool、node、npm、react-native 等等

编译和打包

举个栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# release.sh

npm install

react-native bundle --dev false --platform ios \
--bundle-output "/tmp/main.jsbundle" --entry-file index.ios.js

xctool -project ios/MyApp.xcodeproj -scheme MyApp build -sdk iphoneos \
configuration Release OBJROOT=$PWD/build SYMROOT=$PWD/build

if [[ $? != 0 ]];then
echo ">> build failed"
exit 1;
fi

PROVISIONING_PROFILE="$HOME/Library/MobileDevice/Provisioning Profiles/MyAppdev.mobileprovision"
OUTPUTDIR="$PWD/build/Release-iphoneos"
NAME=Esports_${TRAVIS_COMMIT:0:6}.ipa

xcrun -log -sdk iphoneos PackageApplication "$OUTPUTDIR/MyApp.app" \
-o "$OUTPUTDIR/$NAME"

if [[ $? != 0 ]];then
echo ">> package application failed"
exit 1;
fi

cd $OUTPUTDIR
curl --ftp-create-dirs -T "$NAME" -u $FTP_USER:$FTP_PASSWORD \
ftp://$FTP_SERVER/esports/$NAME

echo ">> {$NAME} uploaded!"
echo ">> all done!"

基本上和本地编译运行 React Native 项目类似,不同之处在于使用了 xctool 命令行来编译

  • 首先安装依赖 package
  • 然后 react-native bundle (如果使用 online 的模式,可以忽略这一步)
  • 使用 xctool 编译和测试项目
  • 打包 App 为 ipa 文件
  • FTP 发布(Travis CI 支持多种发布方式,可以参考文档实现)
  • 完成,邮件或 IM 通知(如果你需要)

ref: https://www.objc.io/issues/6-build-tools/travis-ci/

最近了解了下 react 和 react native,感觉看到了一条成为「Full-stack Developer」的捷径啊(雾

React

  • 消息同步
    不管是 Web 还是 Native 应用,一个很麻烦的问题就是保持某个 Message 在不同 View 之间的状态同步。
    比如,收到一条消息,需要在未读标签显示状态和数字,然后未读列表中插入一条消息记录。
    阅读消息后,需要从未读中去掉这条记录,同时在已读中新增一条记录,还不能忘了将未读数字减去。
    我们是怎么处理这种情况?
    要在接收消息的 handler 中判断各个 View 当前的状态,然后根据对应的状态写不同的处理代码,阅读消息同理。当然高端点的可能自己搞个 Manager 之类的东西去对这些逻辑进行管理。
    但是,并没有什么用。一旦业务复杂,处理和 Debug 起来就很麻烦了。看上去未读是 1,点进去发现是空,这种场景想必见得也不少了。

      还记得很久之前我们怎么写页面的吗?(多久?大概到 ASP 吧=-=
    

    那时候我们没有这么多烦恼。因为我们是这么干的。不管什么操作,ok,刷新页面,可以保证所有状态都是正确的,so easy!
    React + Flux 所做的正和我们很久之前的做法有点像。数据更新了?重新渲染一遍 View 不就好了。
    但是将整个 DOM 重新渲染一遍是开销很大的,当然不可能真的这么做。
    所以就有了 Virtual DOM。
    其实我们对 View 所做的操作大部分都是修改内容,比如修改某个块里面的文字啦,对某个 list 增删改啦。这些都不需要完全重绘。
    React 做到了「智能」去更新 DOM,只改变需要改变的地方。(嗯,就是不相信你能写出高效正确的操作 DOM 的代码,所以我们都帮你写好啦~(雾

  • 组件化
    通常我们的页面上的控件都是带有不同的状态的,比如选框是否选中等等。而不同情况下状态可能会根据一些参数去变化,这让一般情况下的组件复用变得很麻烦。
    而 React 可以做到类似于函数的给定输入参数,输出固定的状态,可以很方便的实现 Web Components,也不会出现组件状态上的冲突。
    而组件化的开发带来的效率提升是非常高的。

  • 其他
    另外经常看到人说 HTML 嵌在 JS 里面感觉很奇怪是不是模板耦合在代码里面之类的,我倒是觉得这种写法挺简洁优雅的。如果把 JSX 里面的 XML 看作是一个 XML 表示的 Object,这样理解可能会好点吧。当然你也可以使用纯 JS 的方式去写。

好的东西往往是看上去简单实现复杂。
Java 本身虽然不怎么样,但是它可以让不同水平的人写出能够达到标准的代码,依然坚挺不是没有理由的。
所以我多少也有些认同 react 可以让不同水平的前端工程师写出符合效用的代码这个说法了。
算是一点并没有深入研究过前端的人的一点想法吧。

React Native

最近公司同时在做某个应用的 iOS 和 Android 版本,虽然没有参与开发,但是从两边的进程来看,大部分时间其实是把同一份逻辑翻译成 Objective-C 和 Java,但是这也是没办法的事情。
毕竟现有的 Hybrid App 方案表现都不太理想, Native 的优势暂时无法替代。如果可以借鉴 React 的优势和开发效率,同时带来 Native 的体验,excited!
所以 React Native 就顺理成章了。
难得的是 React 没有提「Write once, run anywhere」(JAVA:=-=)
而是 「Learn once, write anywhere」。

目前感觉除了基于 React 本身的那些优势外,React Native 在开发 App 方面也是很爽的

  • 效果
    WebView 总是让我们感觉没有 Native 那么顺畅自然,大概是因为触摸反馈之类的原因吧。React Native 在这方面则没有这个问题,可以非常容易的用到 Native 的触摸和动画效果
  • flex 布局
    熟悉 Web 那一套的话用起来还是很方便的。但是 React Native 的 flex 只是 Web 的 Lite 版,虽然尽力去做 Native 兼容,但是表现能力不如 Web 也不如 Native 是必然的,幸好移动端布局通常没有 Web 那么复杂。
  • 组件化
    React Native 提供了基本组件,通过自己组合,可以做出很多意想不到的效果。加上通过 npm 可以引入其他开源的组件,组合出一个 App 更加容易了。
  • 效率
    从安装 Xcode 到 写出一个新闻列表功能只花了两个小时,虽然之前完全没有接触过 iOS 开发。大公司项目这种提供详细文档和 starterkit 的作风真是太赞。
  • 调试方便
    即时刷新,用调试 Web 的方式调 App 真的很爽
  • 集成到现有 App
    可以将某个 React Native 实现的部分集成到现有 App 中,对已有的业务不产生影响

缺点也有:

  • 平台
    除了一些容易抽象的组件比如 ListView,View,Text 之外,目前很多组件是限于平台的。这点也是可以理解的,毕竟不同平台的 API 千差万别,很难做到通用。所以想要一次开发之后小小修改就可以通吃的可以醒醒了 =-=
    这也导致实际上还是无法绕开去,必须要了解 Native 开发的一些组件和细节。比如在 iOS 中,首先你需要放一个 Tabbar,然后每个 Tabbar Item 里面放一个 Navigator,这和 Native 开发其实是差不多的模式。单纯从做 Web 过来的人,还是需要去学习这些东西才能开始。
    但是比起完整使用 Native 开发,这个学习时间要短得多。
  • 表现能力
    虽然 React Native 的表现能力不如 Native ,但是它并不是为了取代 Native 开发存在的。在需要的领域做得足够好,这就够了。
  • 第三方库
    包括 React 和 React-Native 都有这个问题。事实上由于 Virtual DOM 和 React 革命性的架构方式,以往的第三方库很难做到直接可用了。而 React 的可用第三方库还不够多,当然社区还是挺活跃的,相信这个问题会好转。
  • 成熟度
    随着项目变大和引入的 package 变多,你会发现越来越多的莫名奇妙的出错和 bug,这个时候你需要耐心,放狗去搜。最容易找到解决方案的地方还是 Github 上的 issue list。有很多人会碰到同样的问题,也很可能会有人在下面给出解决的方法或是方向。当然,你也可以 subscribe 这个 issue,等待官方哪天大发慈悲解决掉它。
  • 更新速度
    更新太快有好处也有坏处。当你发现新版本解决了一个问题,迫不及待的想要更新的时候,可能发现:你的 App 挂了;第三方组件挂了;你必须升级一堆 package,并解决可能与之相关的问题。当然啦,大部分情况下还是没有那么蛋疼的。

虽然接触的时间不长,也有碰到一些小坑,但是目前看来,对我这种不会 Native 开发的人来说,React Native 是个非常不错的选择。
也许有时间会写写碰到过的坑和一些学习的建议之类的 :-)

这个问题之前很好解决,使用浏览器 plugin 即可。
但是随着 Chrome 和 Firefox 都先后放弃了 NPAPI plugin,这一方法也行不通了,而且很多人是很讨厌 plugin 的。
但是在默认禁止了 NPAPI 的 Chrome 版本,QQ 依然可以实现快速登录(一键登录),是怎么做到的呢?

原理

其实不难猜。既然不存在 plugin,无法以此来实现浏览器内和本地客户端的直接通信,那么排除其他的黑科技,有一种很简单的方法可以实现这个效果。
那就是在客户端开一个 Server,在浏览器里面请求这个地址。
理论上这样是可以实现的,至于 QQ 是不是用的这种方法,稍微验证下好了。

验证

  • 找一个有 QQ 快速登陆的页面,比如 mail.qq.com

  • 登陆 QQ 客户端

  • 打开浏览器的 Developer Tools -> Network

  • 刷新页面,观察所有请求的 domain

  • 好明显,这就是我们要找的了

    • 完整请求 url 是这样的

      1
      https://localhost.ptlogin2.qq.com:4301/pt_get_uins?callback=ptui_getuins_CB&r=0.22949112393586502&pt_local_tk=-2027291081
    • 看看这个请求的 Response Content

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      var var_sso_uin_list = [
      {
      account: "xxxxxx",
      client_type: 65793,
      face_index: 0,
      gender: 1,
      nickname: "xxx",
      uin: "xxx",
      uin_flag: xxxxx,
      },
      ];
      ptui_getuins_CB(var_sso_uin_list);

      很明显是当前登录的用户信息

    • ping 一下这个请求的 domain
      不出所料结果是 127.0.0.1

      1
      2
      ping localhost.ptlogin2.qq.com
      :: Pinging localhost.ptlogin2.qq.com [127.0.0.1] with 32 bytes of data
  • 现在我们验证下是否是 QQ 开了这个 Server

    • 查看哪个程序占用了 4301 端口

      1
      2
      netstat -ano | findstr "4301"
      :: TCP 127.0.0.1:4301 0.0.0.0:0 LISTENING 14516
    • 得到 pid 我们就可以看否是 QQ 在监听这个端口了

      1
      2
      tasklist | findstr "14516"
      :: QQ.exe 14516 Console 2 87,892 K

安全

可能有人担心会不会有安全问题,会不会其他网站访问这个 url 就拿走用户信息?其实挺容易解决,存一个 token 到服务端,获取的时候校验下就好了。
但是归根到底取决于腾讯对这方面安全的重视程度和意愿了,至少之前是确实存在从网页上获取当前登录的 QQ 信息的方法,虽然问题不是出在快速登录这部分。

越来越多的网站开始更加关注安全问题了,比如,Facebook 会在你把密码从 abc123 改为 Abc123 的时候的时候提示你

Your new password is too similar to your current password. Please try another password.

很贴心是不是?但是,他们是怎么做到的?难道 Facebook 保存了用户的明文密码么?

编辑距离

计算两个字符串的相似性,或者说「编辑距离」很容易,我们有很多现成的算法和代码。
但是,显然 Facebook 不会傻到存储明文密码,存储的肯定是 hash("abc123")
而字符串中的差别和 hash 结果并不是一一对应的。两个相近的字符串,其 hash 结果可能差别很大。

simhash

可能你听说过 simhash 算法。Google 就是使用这种算法来做网页查重的。
传统的 hash 算法如 md5,一般尽可能要求结果分布均匀,因此,原始字符串的微小变动也会导致 hash 结果出现很大差异。
而 simhash 是一种局部敏感的 hash 算法,选定位数,提取特征,然后对每一段特征值计算 hash,然后将每一段值处理到 simhash 结果,得到最后的 simhash 值。比较海明距离就可以大概知道两个文档的相似度了。
具体的算法懂得不多就不瞎说了……大概可以推测,对于长文档这种方法是有效的,但是对于短文本,如 password 来说,效果可能不会太好。

Facebook 的做法

事实上这个问题好奇的人也很多,Stack Exchange 上有一个回答 http://security.stackexchange.com/questions/53481/does-facebook-store-plain-text-passwords,下面有一个自称接触过密码验证这部分代码的人肯定了这个猜测。
我觉得这种做法看起来还是挺合理的。

Facebook 用了一种看起来很「土」的方法,操作方法类似这样:

  1. 用户注册,密码 abc123,Facebook 保存了 `hash(“abc123”)
  2. 用户修改密码,提交新密码 Abc123
  3. Facebook 拿到新密码,根据这个密码,生成一堆类似于 ABC123abc123 这样相近的密码,使用同样的 hash 方法,去和 1 中的 hash 比对,一旦发现有相同的,那么可以判定新密码与旧密码是相似的。

很好的反向思维,不是去计算相似性,而是通过生成一堆相似密码来「暴力」尝试。

其他想法

事实上最初想到这个问题的时候,我考虑过这样的做法:

  1. 用户修改密码操作时,提示输入原密码
  2. server 临时记录用户的原密码
  3. 用户输入新密码,server 比对新旧密码
  4. 完成修改或过期后,销毁临时记录

不过即使是临时记录依然可能存在风险,如果需要较高的安全性的话这种方法是不可取的。