マルチチェーン対応は、想像以上に複雑だ

初めてのマルチチェーンウォレット統合の実践的考察 ようやく時間ができてコードを整理できるようになりました。Web3プロジェクトでマルチチェーンウォレット接続機能を導入する際、主にEthereum、Polygon、BSC、Solanaが関わってきます。一見すると「互換性のあるロジックをいくつか追加するだけ」のように思えますが、実際に実装してみると、多くのことが思ったほど単純ではないと気づきました。 this.networkConfigs = { ethereum: { chainId: '0x1', // 1 chainName: 'Ethereum Mainnet', nativeCurrency: { name: 'Ethereum', symbol: 'ETH', decimals: 18 }, rpcUrls: ['https://eth-mainnet.public.blastapi.io'], blockExplorerUrls: ['https://etherscan.io'] }, polygon: { chainId: '0x89', // 137 chainName: 'Polygon Mainnet', nativeCurrency: { name: 'MATIC', symbol: 'MATIC', decimals: 18 }, rpcUrls: ['https://polygon-rpc.com'], blockExplorerUrls: ['https://polygonscan.com'] }, bsc: { chainId: '0x38', // 56 chainName: 'BNB Smart Chain', nativeCurrency: { name: 'BNB', symbol: 'BNB', decimals: 18 }, rpcUrls: ['https://bsc-dataseed.binance.org'], blockExplorerUrls: ['https://bscscan.com'] } } マルチチェーンは単に「複数のウォレットをサポートする」ことではない 最も強く感じたのは、チェーンが異なればウォレットのインタラクション方法も異なり、SDKの考え方すら違うということです。イーサリアムエコシステムでは統一されたWeb3.jsで多くのロジックを処理できますが、Solanaになると、完全に別のシステムであることがわかります。プロバイダーの接続、接続フロー、PublicKeyの構築方法が異なり、ネットワークの遅延や安定性までもがユーザー体験に影響を与えます。 ...

2025年7月5日 · 10 分 · MoeJue

[メモ] Git よく使うコマンド大全(シーン別)

日常の開発でよく使われるGitの操作をまとめたものです。カテゴリ別に整理されているため、すぐに使い始めたい方や、コマンドを確認したい時に役立ちます。 📦 基本コマンド コマンド 役割 git init Gitリポジトリを初期化します(.gitフォルダを作成)。 git clone <url> リモートリポジトリをローカルにクローンします。 git status 現在のワーキングディレクトリの状態(ファイルの変更、ステージングなど)を確認します。 git add <file> ファイルをステージングエリアに追加します。 git commit -m "message" ステージングエリアのファイルをローカルリポジトリにコミットし、コミットメッセージを追加します。 git log コミット履歴を表示します。 🌱 ブランチ管理 コマンド 役割 git branch ローカルブランチの一覧を表示します。 git branch <branch-name> 新しいブランチを作成します。 git checkout <branch-name> 指定したブランチに切り替えます。 git switch <branch-name> 指定したブランチに切り替えます(推奨される新しいコマンド)。 git merge <branch-name> 指定したブランチを現在のブランチにマージします。 git branch -d <branch-name> マージ済みのブランチを削除します。 git branch -D <branch-name> ブランチを強制的に削除します。 🌍 リモートリポジトリ操作 コマンド 役割 git remote -v リモートリポジトリのURLを表示します。 git remote add <name> <url> ...

2025年6月2日 · 2 分 · MoeJue

Web3.0とは何か?ブロックチェーンとの関係は?

また一つ、よくわからない言葉が出てきましたね-_-。以下に要点をまとめてみました。 web3.0とは何か Web 3.0(Web3)は、次世代のインターネットの発展方向であり、より分散化され、ユーザーがコントロールし、データが保護されるネットワーク環境を構築することを目指しています。これまでのWeb 1.0(静的なウェブページ)やWeb 2.0(動的でインタラクティブなウェブページやソーシャルメディア)とは異なり、Web 3.0の核心的な理念と技術には以下の側面が含まれます。 非中央集権化: ブロックチェーン技術:Web 3.0の核心はブロックチェーン技術であり、すべての取引やデータの変更を記録する分散型台帳を提供します。これは、データが単一の中央サーバーによって管理されるのではなく、複数のノードに分散されることを意味します。 分散型アプリケーション(DApps):これらのアプリケーションはブロックチェーンネットワーク上で実行され、中央集権的な管理者が存在しません。DAppsのユーザーは、仲介機関を介さずに直接取引や対話を行うことができます。 ユーザーコントロールとデータ保護: 自己主権型アイデンティティ:Web 3.0では、ユーザーは自身のデジタルアイデンティティとデータを完全にコントロールできます。暗号技術を通じて、ユーザーは誰が自分のデータにアクセスできるか、またそのデータをどのように使用するかを決定できます。 データ所有権:ユーザーのデータは分散型ネットワーク上に保存され、大手企業による集中管理ではなく、ユーザー自身がデータの所有権を持ちます。 スマートコントラクト: 自動実行プロトコル:スマートコントラクトは、ブロックチェーン上で実行される自動化されたプロトコルであり、特定の条件が満たされると自動的に実行されます。この技術は、金融取引、サプライチェーン管理、法的な契約など、さまざまなシーンで利用できます。 相互運用性:Web 3.0は、異なるブロックチェーンや分散型ネットワーク間の相互運用性を実現し、データや資産が異なるプラットフォーム間でシームレスに移動できるようにすることを目指しています。 トラストレスと透明性: トラストレスなメカニズム:Web 3.0はブロックチェーン技術を通じてトラストレスなメカニズムを実現します。つまり、参加者はお互いを信頼したり、第三者の仲介者に依存したりすることなく、安全な取引や対話を行うことができます。 透明性:すべての取引やデータの変更はブロックチェーン上に記録され、公開され透明であり、改ざん不可能です。これにより、信頼性とセキュリティが向上します。 Web 3.0の応用シーン 分散型金融(DeFi):スマートコントラクトとブロックチェーン技術を通じて、貸付、取引、投資などの仲介者を必要としない金融サービスを提供します。 デジタルアイデンティティ:ユーザーは自身のデジタルアイデンティティを作成・管理し、個人データのアクセスと使用をコントロールできます。 サプライチェーン管理:ブロックチェーン技術を通じて、サプライチェーンの透明性と追跡可能性を実現し、偽造や詐欺を防止します。 コンテンツ制作と配信:アーティスト、ミュージシャン、作家は、直接視聴者と対話し取引を行うことで、より公平な収益分配を得ることができます。 では、ブロックチェーンとは何か? ブロックチェーン技術は、分散型台帳技術(Distributed Ledger Technology, DLT)の一種であり、取引や情報を記録するための改ざん不可能で非中央集権的なデータベースです。ブロックチェーン技術の核心的な考え方は、非中央集権的な方法でデータの安全性、透明性、信頼性を実現することです。 ブロックチェーンの基本原理 ブロック(Block): ブロックは取引記録を含むデータパッケージです。各ブロックには複数の取引と、そのブロックを一位に識別するためのハッシュ値が含まれています。 ブロックには前のブロックのハッシュ値も含まれており、これによりすべてのブロックが鎖のようにつながり、ブロックチェーンを形成します。 チェーン(Chain): ブロックはハッシュ値によって相互に連結され、チェーン構造を形成します。各ブロックが前のブロックのハッシュ値を含むため、ブロックチェーン内のデータを改ざんすることは非常に困難です。 あるブロックのデータを変更しようとすると、そのブロックおよびそれに続くすべてのブロックのハッシュ値が変化するため、これらすべてのブロックのハッシュ値を再計算する必要があり、これはほぼ不可能です。 非中央集権化(Decentralization): ブロックチェーンネットワーク内のすべてのノード(コンピュータ)は、ブロックチェーンの完全なコピーを保持しており、これらのコピーはコンセンサスメカニズムによって一貫性が保たれています。 中央機関がブロックチェーンを管理・制御することはなく、データはすべてのノードによって共同で維持されます。 コンセンサスメカニズム(Consensus Mechanism): ブロックチェーンネットワークは、すべてのノードがブロックチェーンの状態について合意に達することを保証するためにコンセンサスメカニズムを使用します。一般的なコンセンサスメカニズムには、プルーフ・オブ・ワーク(Proof of Work, PoW)やプルーフ・オブ・ステーク(Proof of Stake, PoS)があります。 プルーフ・オブ・ワーク(PoW):マイナーが複雑な数学的問題を解くことで取引を検証し、ブロックチェーンに追加します。問題を解くには大量の計算能力が必要であり、これによりブロックチェーンのセキュリティが確保されます。 プルーフ・オブ・ステーク(PoS):バリデーターが保有する暗号資産の量やその他の要因に基づいて取引を検証し、それに応じた報酬を得ます。 暗号技術(Cryptography): ブロックチェーンは、データの安全性とプライバシーを確保するために暗号技術を使用します。各取引は公開鍵と秘密鍵を使用して署名・検証され、正当な所有者のみが取引を開始できるようにします。 ブロックチェーンのメリット セキュリティ: データは複数のノードに分散されているため、単一障害点がなく、攻撃者がデータを改ざんすることは困難です。各ブロックには前のブロックのハッシュ値が含まれており、一つのブロックを改ざんするにはチェーン全体を変更する必要があるため、コストが非常に高くなります。 透明性: ブロックチェーン上のすべての取引記録は公開されており、誰でも閲覧できます。取引記録は改ざん不可能であり、システムの透明性と信頼性を高めます。 非中央集権化: 中央管理機関が存在せず、すべてのノードが平等にネットワークの維持・管理に参加するため、単一の支配や権力の集中を防ぎます。 改ざん耐性: データが一度ブロックチェーンに書き込まれると、それを改ざんすることは非常に困難であり、データの完全性と真正性が保証されます。 ブロックチェーンの応用シーン 暗号資産(仮想通貨): ビットコイン(Bitcoin)は、最初で最も有名なブロックチェーンアプリケーションであり、非中央集権的な方法でピアツーピアの電子現金システムを実現します。 イーサリアム(Ethereum)は、暗号資産の取引だけでなく、スマートコントラクトの実行もサポートしており、ブロックチェーンの応用範囲を広げています。 サプライチェーン管理: ...

2024年6月17日 · 1 分 · MoeJue

感熱ブルートゥースプリンター開発

最近、レシート印刷の作業をしており、プロジェクトの要件としてiOSとAndroidの両方で実装する必要がありました。最初は全く分からず、インターネットで多くの資料を探し、たくさんの落とし穴にはまり、多くの記事を読みましたが、結果的にはうまくいきました。 Bluetoothプリンターは一般的に、レシート印刷とラベル印刷の2種類の印刷モードに分けられます。 会社が購入した粗悪なプリンターには開発ドキュメントすらなく、多くの落とし穴にはまる羽目になりました。開発担当者に購入時に相談してくれればよかったのに。 現在、WeChatミニプログラムでBluetoothプリンターに接続する wx.createBLEConnection は、iOSデバイスでは問題なく動作しますが、一部のAndroidスマートフォンでは異常が発生します(接続時にシステムペアリングボックスがポップアップ表示され、キャンセルをタップしても、ペアリングコードを入力して確定をタップしても、すぐに接続が切断されます。入力もキャンセルもしない場合、30秒以内にBluetoothプリンターから自動的に切断されます)。 現在採用している方法は、AndroidとiOSそれぞれにBluetooth印刷コマンドのセットを作成することです。 IOS // ====================蓝牙操作================== //初始化蓝牙模块 openBluetoothAdapter() { if (app.sysinfo.provider == 1) { // 开启蓝牙 app.onBluetooth() setTimeout(() => { this.android_search() }, 2000) return false; } this.closeBluetoothAdapter() uni.openBluetoothAdapter({ success: (res) => { console.log("初始化蓝牙模块: " + JSON.stringify(res)); this.startBluetoothDevicesDiscovery() }, fail: (res) => { if (res.errCode === 10001) { uni.onBluetoothAdapterStateChange((res) => { console.log('监听蓝牙适配器状态变化事件', res) if (res.available == false) { app.global_printing = {} this.connected = false this.chs = [] this.canWrite = false } if (res.available) { this.startBluetoothDevicesDiscovery() } }) } if (res.errCode) { app.alert('初始化蓝牙失败,错误码:' + res.errCode) return false; } app.alert(res.errMsg) } }) }, //获取本机蓝牙适配器状态 getBluetoothAdapterState() { uni.getBluetoothAdapterState({ success: (res) => { console.log('获取本机蓝牙适配器状态。', JSON.stringify(res)) if (res.discovering) { this.onBluetoothDeviceFound() } else if (res.available) { this.startBluetoothDevicesDiscovery() } }, fail: (res) => { console.log('error:获取本机蓝牙适配器状态失败', JSON.stringify(res)) setTimeout(() => { this.getBluetoothAdapterState() }, 500) } }) }, //开始搜寻附近的蓝牙外围设备 startBluetoothDevicesDiscovery() { console.log(this.discoveryStarted); if (this.discoveryStarted) { return } console.log('开始搜索蓝牙设备'); this.discoveryStarted = true this.onBluetoothDeviceFound() setTimeout(() => { uni.startBluetoothDevicesDiscovery({ allowDuplicatesKey: true, success: (res) => { console.log('startBluetoothDevicesDiscovery success', JSON.stringify( res)) }, fail: (res) => { if (res.errCode == '10001') { app.alert('当前蓝牙适配器不可用') } else { app.alert('搜索蓝牙失败,状态码:' + res.errCode) } } }) }, 500) }, // 停止搜索 stopBluetoothDevicesDiscovery() { uni.stopBluetoothDevicesDiscovery() this.discoveryStarted = false }, //寻找到新设备的事件的回调函数 onBluetoothDeviceFound() { console.log('寻找到新设备的事件的回调函数'); uni.onBluetoothDeviceFound((res) => { console.log(res); res.devices.forEach(device => { if (!device.name && !device.localName) { return } const foundDevices = this.devices const idx = this.inArray(foundDevices, 'deviceId', device.deviceId) if (idx === -1) { this.devices.push(device) } else { this.devices[idx] = device } }) }) }, //连接低功耗蓝牙设备 createBLEConnection(e) { uni.showLoading({ title: '设备连接中', mask: true }); const ds = e.currentTarget.dataset const deviceId = ds.deviceId const name = ds.name if (app.sysinfo.provider == 1) { if (ds.pair !== true) { this.android_search(deviceId) } else { console.log('已配对') } var device = null, BAdapter = null, BluetoothAdapter = null, uuid = null, main = null, bluetoothSocket = null; var mac_address = deviceId var main = plus.android.runtimeMainActivity(); BluetoothAdapter = plus.android.importClass("android.bluetooth.BluetoothAdapter"); var UUID = plus.android.importClass("java.util.UUID"); uuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); BAdapter = BluetoothAdapter.getDefaultAdapter(); device = BAdapter.getRemoteDevice(mac_address); plus.android.importClass(device); bluetoothSocket = device.createInsecureRfcommSocketToServiceRecord(uuid); plus.android.importClass(bluetoothSocket); if (!bluetoothSocket.isConnected()) { console.log('检测到设备未连接,尝试连接....'); bluetoothSocket.connect(); } this.connected = true this.name = name this.deviceId = deviceId this.canWrite = true app.global_printing = { name: name, deviceId: deviceId } app.saveData1('global_printing', app.global_printing) uni.hideLoading(); return false; } uni.createBLEConnection({ deviceId, success: (res) => { this.connected = true this.name = name this.deviceId = deviceId app.global_printing = { name: name, deviceId: deviceId } this.onBLEConnectionStateChange() // 防止获取失败 setTimeout(() => { this.getBLEDeviceServices(deviceId) }, 1000) }, fail: (res) => { uni.hideLoading(); app.Toast('设备连接失败') console.log("蓝牙连接失败:", res); } }) this.stopBluetoothDevicesDiscovery() }, //获取蓝牙设备所有服务(service) getBLEDeviceServices(deviceId) { uni.getBLEDeviceServices({ deviceId, success: (res) => { console.log("获取蓝牙服务成功:" + JSON.stringify(res)) if (res.services.length == 0) { uni.hideLoading(); app.alert('没有获取到蓝牙服务,无法打印001') app.global_printing = {} return false } for (let i = 0; i < res.services.length; i++) { if (res.services[i].isPrimary) { this.getBLEDeviceCharacteristics(deviceId, res.services[i].uuid) return } } }, fail: (res) => { setTimeout(() => { this.getBLEDeviceServices(deviceId) }, 500) console.log("获取蓝牙服务失败:" + JSON.stringify(res)) } }) }, //获取蓝牙设备某个服务中所有特征值(characteristic) getBLEDeviceCharacteristics(deviceId, serviceId) { console.log('获取蓝牙设备某个服务中所有特征值', deviceId, serviceId) uni.getBLEDeviceCharacteristics({ deviceId, serviceId, success: (res) => { console.log('获取蓝牙设备某个服务中所有特征值 success', JSON.stringify(res)) uni.hideLoading(); if (res.characteristics.length == 0) { app.alert('没有获取到蓝牙服务,无法打印002') app.global_printing = {} return false } for (let i = 0; i < res.characteristics.length; i++) { let item = res.characteristics[i] if (item.properties.read) { uni.readBLECharacteristicValue({ deviceId, serviceId, characteristicId: item.uuid, }) } if (item.properties.write) { this.canWrite = true app.global_printing._deviceId = deviceId app.global_printing._serviceId = serviceId app.global_printing._characteristicId = item.uuid app.saveData1('global_printing', app.global_printing) //this.writeBLECharacteristicValue() } if (item.properties.notify || item.properties.indicate) { uni.notifyBLECharacteristicValueChange({ deviceId, serviceId, characteristicId: item.uuid, state: true, }) } } }, fail(res) { console.error('获取特征值失败:', res) } }) // 操作之前先监听,保证第一时间获取数据 uni.onBLECharacteristicValueChange((characteristic) => { console.log(this.data.chs); const idx = this.inArray(this.data.chs, 'uuid', characteristic.characteristicId) const data = {} if (idx === -1) { this.chs[this.data.chs.length] = { uuid: characteristic.characteristicId, value: ab2hex(characteristic.value) } } else { this.chs[idx] = { uuid: characteristic.characteristicId, value: ab2hex(characteristic.value) } } }) }, onBLEConnectionStateChange() { uni.onBLEConnectionStateChange((res) => { // 该方法回调中可以用于处理连接意外断开等异常情况 console.log(`蓝牙连接状态改变device ${res.deviceId} state has changed, connected: ${res.connected}`) if (res.connected == false) { app.global_printing = {} this.connected = false this.chs = [] this.canWrite = false } }) }, //断开与低功耗蓝牙设备的连接 closeBLEConnection() { app.global_printing = {} uni.closeBLEConnection({ deviceId: this.deviceId }) this.connected = false this.chs = [] this.canWrite = false }, //关闭蓝牙模块 closeBluetoothAdapter() { app.global_printing = {} uni.closeBluetoothAdapter() this.discoveryStarted = false }, //发送数据 sendStr(bufferstr, success, fail) { var that = this; uni.writeBLECharacteristicValue({ deviceId: app.global_printing._deviceId, serviceId: app.global_printing._serviceId, characteristicId: app.global_printing._characteristicId, value: bufferstr, success: function(res) { success(res); console.log('发送的数据:' + bufferstr) // console.log('message发送成功') }, fail: function(res) { fail(res) console.log("数据发送失败:" + JSON.stringify(res)) }, complete: function(res) { // console.log("发送完成:" + JSON.stringify(res)) } }) }, //遍历发送数据 printCode(arr) { var that = this; if (arr.length > 0) { this.sendStr(arr[0], function(success) { arr.shift(); that.printCode(arr); }, function(error) { app.alert('打印失败,错误码:' + error.errCode) app.printing_status = false console.log(error); }); return false; } setTimeout(function() { app.printing_status = false console.log('打印结束'); }, 1000); }, Android 比較的シンプルで便利です。Native.jsを使用してNative Javaインターフェースチャネルを直接呼び出し、plus.androidを介してAndroidネイティブシステムAPIを呼び出します。 ネイティブAndroidドキュメント https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter?hl=en ...

2019年11月5日 · 9 分 · MoeJue

WeChatでログイン

ドキュメント:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319 WeChatの認証ログインは、QQやSinaなどのプラットフォームの認証ログインとほぼ同じで、いずれもOAuth2.0認証方式を採用しています。 WeChatの認証は2種類に分かれています: サイレント認証 ポップアップ認証、ユーザーの手動同意が必要 2種類のscopeの違いについて snsapi_baseをscopeとして開始されるウェブ認証は、ページにアクセスしたユーザーのopenidを取得するためのもので、サイレント認証であり、自動的にコールバックページにリダイレクトされます。ユーザーは直接コールバックページ(通常は業務ページ)にアクセスしたように感じます。 snsapi_userinfoをscopeとして開始されるウェブ認証は、ユーザーの基本情報を取得するためのものです。この認証はユーザーの手動同意が必要ですが、一度同意すれば、公式アカウントをフォローしていなくても、認証後にそのユーザーの基本情報を取得できます。 ユーザー管理関連のAPIにおける「ユーザー基本情報取得API」は、ユーザーが公式アカウントとメッセージのやり取りをするか、フォローした後のイベントプッシュがあって初めて、ユーザーのOpenIDに基づいて基本情報を取得できます。このAPIを含む他のWeChat APIは、すべて対象ユーザー(openid)が公式アカウントをフォローしている場合にのみ、正常に呼び出すことができます。 具体的には、ウェブ認証のフローは4つのステップに分かれています: ユーザーを認証ページに誘導し、同意を得てcodeを取得する codeを使用してウェブ認証用のaccess_tokenを取得する(基本サポートのaccess_tokenとは異なる) 必要に応じて、開発者はウェブ認証用のaccess_tokenを更新し、期限切れを回避する ウェブ認証用のaccess_tokenとopenidを使用してユーザーの基本情報を取得する(UnionIDメカニズムをサポート) 以下はカプセル化されたWeChat操作クラスです。access_tokenとticketを保存するために2つのデータテーブルを使用します。これらには有効期限があり、1日あたりのリクエスト数に上限があるため、開発者は自身で保存する必要があります。 <?php /** * WeChat操作テーブル * wxtoken テーブル構造 * id * access_token * addtime * wxticket テーブル構造 * id * ticket * addtime */ class WX { private $appid; private $appserect; private $curl; private $msg; protected $errs = array( '-1' => 'システムがビジーです。開発者はしばらくしてから再試行してください', '0' => 'リクエスト成功', '40001' => 'AppSecretが間違っているか、この公式アカウントのものではありません。開発者はAppSecretが正しいことを確認してください', '40002' => 'grant_typeフィールドの値がclient_credentialであることを確認してください', '40164' => 'APIを呼び出したIPアドレスがホワイトリストにありません。APIのIPホワイトリストで設定してください。', ); function __construct($appid, $appserect) { $this->appid = $appid; $this->appserect = $appserect; $this->curl = new Curl(); } /* WeChatウェブ認証ログイン 公式アカウント設定 - 機能設定 - ウェブ認証ドメインでの設定が必要 第一歩:ユーザーが認証に同意し、codeを取得 scope : snsapi_base openidのみ取得可能、直接リダイレクト snsapi_userinfo */ public function getCode($redirect_uri, $scope = 'snsapi_userinfo',$state = '1') { $url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid={$this->appid}&redirect_uri={$redirect_uri}&response_type=code&scope={$scope}&state={$state}#wechat_redirect"; header("Location:{$url}"); exit; } /* 第二歩:codeを使用してウェブ認証用のaccess_tokenを取得 */ public function getAccessTokenByCode($code) { $url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid={$this->appid}&secret={$this->appserect}&code={$code}&grant_type=authorization_code"; // exit($url); // $curl = new Curl(); $result = $this->curl->doGet($url); if (!$result) { // $this->curl->getError() $this->msg = "トークンの取得に失敗しました"; return false; } $result = json_decode($result, true); if ($result['errcode']) { $this->msg = $result['errmsg']; return false; } return $result; } // 第三歩:access_tokenを更新(必要な場合) code経由でopenidを取得 $type 0サイレント認証 1ポップアップ認証 public function getUserInfo($code, $type = 0, $lang = 'zh_CN ') { $result = $this->getAccessTokenByCode($code); if (!$result) { return false; } $member = C::t(PT_USER)->getByOpenid($result['openid']); if ($member) { return $member; } else { if ($type) { $url = "https://api.weixin.qq.com/sns/userinfo?access_token={$result['access_token']}&openid={$result['openid']}&lang={$lang}"; // $return = $this->curl->doGet($url); // このAPIは挙動がおかしく、強制的にファイルヘッダーを表示します $return = file_get_contents($url); if (!$return) { $this->msg = 'ユーザー情報の取得に失敗しました'; return false; } $return = json_decode($return, true); if (!$return) { $this->msg = 'ユーザー情報の取得リターンに失敗しました'; return false; } // file_put_contents('ccc.txt',print_r($return,true),FILE_APPEND); $data = array( 'openid' => $return['openid'], 'name' => $return['nickname'], 'sex' => $return['sex'], 'province' => $return['province'], 'city' => $return['city'], 'country' => $return['country'], 'img' => $return['headimgurl'], 'bindtel' => 0, ); } else { $data = array( 'openid' => $result['openid'], 'username' => "WeChatユーザー_" . random(6,1) ); } $name = rand(100000, 1000000000); $e = $name . "@qq.com"; $password = $e; $id = UserAddEdit(0, $data['username'], $password, $e,10,0,"", $msg); if ($id <= 0) { $this->msg = $msg; return false; } C::t(PT_USER)->update($data, $id); $member = C::t(PT_USER)->get($id); return $member; } } /* 公式アカウント セキュリティセンターでIPホワイトリストを設定 公式アカウントのグローバルでユニークなAPI呼び出し資格情報。公式アカウントが各APIを呼び出す際にはaccess_tokenを使用する必要があります。開発者は適切に保存する必要があります。access_tokenの保存には少なくとも512文字のスペースを確保してください。access_tokenの有効期間は現在2時間で、定期的に更新する必要があります。重複して取得すると、前回取得したaccess_tokenが無効になります。 */ public function getAccessToken($type) { $addtime = TIMESTAMP - 7200; $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$this->appid}&secret={$this->appserect}"; $row = C::t(PT_WXTOKEN)->getNew($addtime, $type); if ($row) { return $row['access_token']; } else { $result = $this->curl->doGet($url); if (!$result) { $this->msg = "トークンコンテンツを取得できません"; return false; } $result = json_decode($result, true); if (!$result) { $this->msg = "トークンコンテンツの解析に失敗しました"; return false; } if ($result['access_token']) { C::t(PT_WXTOKEN)->addToken($result['access_token'], $type); return $result['access_token']; } else { $this->msg = "トークンの取得に失敗しました"; return false; } } } // JSチケットを取得 公式アカウント設定 - 機能設定 - JSインターフェースセキュアドメインでの設定が必要 public function getJsTicket() { $addtime = TIMESTAMP - 7200; $row = C::t(PT_WXTICKET)->getNew($addtime); if ($row) { return $row['ticket']; } else { $token = $this->getAccessToken(); if (!$token) { return false; } $url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={$token}&type=jsapi"; $result = $this->curl->doGet($url); if (!$result) { $this->msg = "JSチケットを取得できません"; return false; } $result = json_decode($result, true); if (!$result) { $this->msg = "JSチケットコンテンツの解析に失敗しました"; return false; } if ($result['ticket']) { C::t(PT_WXTICKET)->addTicket($result['ticket']); return $result['ticket']; } else { $this->msg = "JSチケットの取得に失敗しました"; return false; } } } // js sdk チケット署名 現在のウェブページのURL、#およびそれ以降の部分は含まない public function jsSign($data) { // 1.署名対象のすべてのパラメータをフィールド名のASCIIコード順(辞書順)でソートする ksort($data); // 2.URLキー・バリューペアの形式(つまりkey1=value1&key2=value2…)で文字列string1に連結する。元の値を使用し、URLエンコードは行わない $string1 = $this->ToUrlParams($data); // echo "string1:{$string1}<br/>"; // 3.string1をsha1で暗号化する $sign = sha1($string1); // echo "signature:{$sign}<br/>"; return $sign; } // メッセージ内容を取得 public function getMsg() { return $this->msg; } /** * パラメータをURLパラメータ形式にフォーマットする */ public function ToUrlParams($data) { $buff = ""; foreach ($data as $k => $v) { if ($k != "sign" && $v != "" && !is_array($v)) { $buff .= $k . "=" . $v . "&"; } } $buff = trim($buff, "&"); return $buff; } } ?> // WeChatログイン function wxlogin() { global $_G,$identifier,$config,$wx; if (!$_G['uid']) { if ($_GET['state']) { //コールバック $member = $wx->getUserInfo($_GET['code']); if (!$member) { exit($wx->getMsg()); } if (!function_exists("setloginstatus")) { include_once libfile('function/member'); } // ログイン状態を設定$wx setloginstatus($member, 2592000); checkfollowfeed(); $_G['uid'] = $member['uid']; $_G['member'] = $member; } else { //認証をリクエスト パラメータをエンコード $redirect = urlencode(getProtocol() . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']); $wx->getCode($redirect, 'snsapi_base'); } } } function getProtocol() { return is_HTTPS() ? 'https://' : 'http://'; } function is_HTTPS() { if ($_SERVER['HTTPS'] === 1 || $_SERVER['HTTPS'] === 'on' || $_SERVER['SERVER_PORT'] == 443) { return true; } return false; }

2019年4月5日 · 4 分 · MoeJue

WeChat JSAPI 決済

以前からWeChat関連の業務を行っていましたが、新しい技術ではないものの、これまで触れる機会がなく、いくつかの落とし穴にはまりました。そこで、時間を取って整理し、記録しておきます。 WeChat Payは全部で7種類あり、それぞれ:支払いコード決済、JSAPI決済、Native決済、APP決済、H5決済、ミニプログラム決済、顔認証決済です。 今回の業務で使用したのはWeChat JSAPI決済です。ユーザーがWeChatスキャン、公式アカウントのフォローなどの方法で販売者のH5ページに入り、WeChat内でJSSDKを呼び出して支払いを完了します。 ドキュメント:https://pay.weixin.qq.com/wiki/doc/api/index.html SDK:[https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=11_1 ](https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=11_1)JSAPI決済は、WeChat内のブラウザで開くことでWeChat Payを起動できます。効果は以下の図の通りです。 インターフェースコードを添付します。 コードではテンプレートエンジンを使用しています。 html: {$competition\['username'\]} {$prices\[0\]}钻 {$prices\[1\]}钻 {$prices\[2\]}钻 {$prices\[3\]}钻 {$prices\[4\]}钻 注:1钻=1元,1钻={$activity\['offset'\]}票 立即微信支付 JavaScript: // 投票 var offset = {$activity[‘offset’]}; $(’#tips’).html(‘正在给{$competition[‘code’]}号赠送{$prices[0]}钻=’+({$prices[0]}*offset)+‘票’); $(’#vote’).click(function(){ $.post(’/index/index/detailed.html?cid={$cid}&aid={$aid}’,{ formhash :’{FORMHASH}’, submit:‘1’, type:1, openid:’{$_G[‘member’][‘openid’]}’ },function(res){ alert(res.msg); if (res.code == 0) { $(’.box-1 span’).text(res.data.all); $(’.box-2 span’).text(res.data.rank); $(’.box-3 span’).text(res.data.up + ‘票’); } }); }) /* jQuery对象级别プラグイン拡張 */ $.fn.extend({ /* 単選框 */ hlRadio:function () { var radioEl=$(this); radioEl.click(function () { var price = 0; price = $(‘input:radio:checked’).val(); $(’#price’).val(’’); $(’#tips’).html(‘正在给{$competition[‘code’]}号赠送’+price+‘钻=’+(price*offset)+‘票’); radioEl.siblings(“div”).removeClass(“active”); $(this).siblings(“div”).addClass(“active”); }); }, }); $(“input[name=‘price’]”).hlRadio(); $(’#price’).bind(‘input propertychange’, function(){ var price = 0; price = $(’#price’).val(); $(’#tips’).html(‘正在给{$competition[‘code’]}号赠送’+price+‘钻=’+(price*offset)+‘票’); }) ...

2019年3月30日 · 4 分 · MoeJue

[ノート]Git常用コマンド大全

前回に引き続き、時間を見つけてGitの主要なコマンドをまとめました。非常に素晴らしい、高解像度(1759*3162)のチートシートを見つけました。 ファイルの表示、追加、コミット、削除、復元、変更のリセット git help # のヘルプを表示 git show # 特定のコミットの内容を表示 git show $id git co – # ワークスペースの変更を破棄 git co . # ワークスペースの変更を破棄 git add # 作業ファイルの変更をローカルのステージングエリアにコミット git add . # 変更されたすべての作業ファイルをステージングエリアにコミット git rm # バージョン管理からファイルを削除 git rm –cached # バージョン管理からファイルを削除するが、ファイル自体は削除しない git reset # ステージングエリアから作業ファイルに復元 git reset – . # ステージングエリアから作業ファイルに復元 git reset –hard # 直近のコミットの状態に戻す。つまり、前回のコミット以降のすべての変更を破棄 git ci git ci . git ci -a # git add, git rm, git ciなどの操作を一度にまとめて実行 git ci -am “some comments” ...

2018年6月16日 · 3 分 · MoeJue

LinuxでのPHP環境のインストール

学习环境:Centos 7.2 正式版 # 更新源 yumアップデート 安装 nginx yum install nginx -y 安装mysql wget https://repo.mysql.com//mysql57-community-release-el7-11.noarch.rpm yum localinstall mysql57-community-release-el7-11.noarch.rpm 安装PHP7.2 rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm rpm -Uvh https://mirror.webtatic.com/yum/el7/webtatic-release.rpm yumアップデート yum install -y php72w-cli php72w-common php72w-dba php72w-devel php72w-fpm php72w-gd php72w-imap php72w-mbstring php72w-mysql php72w-pdo php72w-pdo_dblib php72w-pear php72w-pecl-igbinary php72w-pecl-xdebug php72w-process php72w-xml php72w-xmlrpc php72w-opcache php72w-pecl-memcached php72w-pecl-mongodb php72w-pecl-redis 启动服务 サービス nginx スタート サービス mysql 開始 php-fmp サービス開始 /usr/sbin/php-fpm –nodaemonize –fpm-config /etc/php-fpm.conf > /dev/null 2>&1 & 编辑配置文件 vim /etc/nginx/nginx.conf :q!#不保存退出 wq #保存退出 ...

2018年5月7日 · 1 分 · MoeJue

Git の簡単な使用手順

この記事では概念的な知識については詳しく説明しません。ただのメモです。これは単なるステップバイステップのガイドです。障害に遭遇した場合は、Google で調べてください。 SSH を使用して Git と GitHub 間のバインドを完了します 1\。 「SSHキー」を生成する ssh-keygen-trsa RSA アルゴリズムを指定してキーを生成すると、id_rsa と id_rsa.pub という 2 つのファイル、つまりキー id_rsa と公開キー id_rsa.pub が生成されます。これら 2 つのファイルについては、 2\。 SSHキーを追加する github.com -> 設定 -> SSH と GPG -> 新しい SSH キー 公開キー id_rsa.pub の内容をキーの場所に貼り付け (タイトルの内容を入力しなくても問題ありません)、[SSH キーの追加] をクリックします。 3\。バインドが成功したかどうかを確認する ssh -T git@github.com ローカルプロジェクトをgithubにプッシュするコマンド (1) ディレクトリを開きます CDデモ (2) gitファイルを生成するためにバージョンライブラリを初期化する gitの初期化 (3) 全ファイルをキャッシュ領域に追加 git add * (4) 現在のワークスペースに変更を送信します。 git commit -m “最初のコミット” (5) 倉庫をリモートサーバーに接続する git Remote addorigin (上記のウェアハウスのアドレスです) (6) 追加したサーバーに変更をプッシュします ...

2018年4月3日 · 1 分 · MoeJue

.htaccessを利用してサブドメインをサブディレクトリに紐付ける

通常、1つの仮想ホストでは、ルートディレクトリにバインドできるドメインは1つだけです。 emlogを使用したことがある方なら、その静的リンク(パーマリンク)についてよくご存知のはずです。これは、.htaccessファイルを利用してウェブページをドメインリダイレクトする典型的な例です。 .htaccessを利用してドメインをサブディレクトリにバインドするには、前提として、お使いのホスティングサーバーがApacheのrewrite機能をサポートしている必要があります。そうでなければ.htaccessは使用できません。もしお使いのサーバーがLinuxサーバーであれば、通常はデフォルトで有効になっています。これにより、ドメインのサブディレクトリへのバインドや、1つのホスティングスペースで複数のサイトを運営することが可能になります。 以下は、link.52ecy.cn をサブディレクトリ link にバインドする例の.htaccessコードです。 <IfModule mod_rewrite.c> RewriteEngine On RewriteBase / # link.52ecy.cn をサブディレクトリ link にバインド RewriteCond %{HTTP_HOST} ^link\.52ecy\.cn$ [NC] RewriteCond %{REQUEST_URI} !^/link/ RewriteRule ^(.*)$ link/$1?Rewrite [L,QSA] # 複数バインド可能。上の3行を繰り返し、ドメイン名とディレクトリ名を変更するだけです </IfModule> 上記の手順を完了すると、サブドメインにアクセスできるようになるはずです。しかし、ブラウザで「メインドメイン + バインドしたディレクトリ」にアクセスしても表示されることに気づくでしょう。これは我々が望む動作ではありません。 次に、最後のステップを完了させます。バインドした各ディレクトリ(例えばlinkディレクトリ)にも、.htaccessファイルを追加します。 .htaccessのコードは以下の通りです。 以下は、music.xmgho.com をサブディレクトリ music にバインドする例の.htaccessコードです。 これにより、ドメインのサブディレクトリへのバインドや、1つのホスティングスペースで複数のサイトを運営することが可能になります。これにより、ドメインのサブディレクトリへのバインドや、1つのホスティングスペースで複数のサイトを運営することが可能になります。これにより、ドメインのサブディレクトリへのバインドや、1つのホスティングスペースで複数のサイトを運営することが可能になります。これにより、ドメインのサブディレクトリへのバインドや、1つのホスティングスペースで複数のサイトを運営することが可能になります。 <IfModule mod_rewrite.c> RewriteEngine On RewriteBase / # バインドしたドメインからのアクセスのみを許可 RewriteCond %{HTTP_HOST} !^link\.52ecy\.cn$ [NC] RewriteRule (.*) http://link.52ecy.cn/$1 [L,R=301] # バインドしたディレクトリ内の同名ディレクトリの処理 RewriteCond %{REQUEST_URI} ^\/link\/ [NC] RewriteCond %{QUERY_STRING} !^(.*)?Rewrite RewriteRule ^(.*)$ /%{REQUEST_URI}/%{REQUEST_URI}/$1?Rewrite [L,QSA] </IfModule> 結び: 私のサーバー自体がサブドメインのサブディレクトリへのバインドをサポートしているため、自分ではテストできませんでした。しかし、友人にAlibaba Cloud(阿里云)のホストでテストしてもらったところ、成功しました。ただし、Alibaba Cloudには数量制限があり、多数バインドするとルートディレクトリにリダイレクトされるようです。このことから、すべてのホスティングプロバイダーがこの方法をサポートしているわけではないようで、ご自身でテストして確認する必要があります。 最後に、最も重要な点は、ドメインが正しく名前解決されていなければ意味がないということです[苦笑~]

2018年1月22日 · 1 分 · MoeJue