UWInfo Blog
發表新文章
[Join] | [忘記密碼] | [Login]
搜尋

搜尋意見
文章分類-#Author#
[所有文章分類]
所有文章分類
  • ASP.NET (48)
  • ASP.NET2.0 (15)
  • ASP.NET4.0 (34)
  • JavaScript (49)
  • jQuery (26)
  • FireFox (4)
  • UW系統設定 (3)
  • SQL (39)
  • SQL 2008 (25)
  • mirror (4)
  • SVN (4)
  • IE (9)
  • IIS (20)
  • IIS6 (1)
  • 閒聊 (7)
  • W3C (6)
  • 作業系統 (9)
  • C# (24)
  • CSS (12)
  • FileServer (1)
  • HTML 5 (11)
  • CKEditor (3)
  • UW.dll (13)
  • Visual Studio (16)
  • Browser (8)
  • SEO (1)
  • Google Apps (3)
  • 網站輔助系統 (4)
  • DNS (5)
  • SMTP (4)
  • 網管 (11)
  • 社群API (3)
  • SSL (4)
  • App_Inventor (1)
  • URLRewrite (2)
  • 開發工具 (6)
  • JSON (1)
  • Excel2007 (1)
  • 試題 (3)
  • LINQ (1)
  • bootstrap (0)
  • Vue (3)
  • IIS7 (3)
  • foodpanda (2)
  • 編碼 (2)
  • 資安 (3)
  • Sourcetree (1)
  • MAUI (1)
  • CMD (1)
  • my sql (1)
最新回應
  • Newtonsoft.Json.JsonConvert.DeserializeObject 失敗的情況
    test...more
  • dotnet ef dbcontext scaffold
    ...more
  • [ASP.NET] 利用 aspnet_regiis 加密 web.config
    ...more
  • IIS ARR (reverse proxy) 服務安裝
    ...more
  • [錯誤訊息] 請加入 ScriptResourceMapping 命名的 jquery (區分大小寫)
    ...more
  • 用 Javascript 跨網頁讀取 cookie (Cookie cross page, path of cookie)
    ...more
  • 線上客服 - MSN
    本人信箱被盜用以致資料外洩,是否可以請貴平台予以協助刪除該信箱之使用謝謝囉...more
  • 插入文字到游標或選取處
    aaaaa...more
  • IIS 配合 AD (Active Directory) 認証, 使用 .Net 6.0
    太感謝你了~~~你救了我被windows 認證卡了好幾天QQ...more
  • PostgreSQL 的 monitor trigger
    FOR EACH ROW 可能要改為 FOR EACH STATEMENT ...more
標籤
  • @@YOTU3
  • 20
  • 8.5Oucg
  • 20131018
  • 悟
  • [t],
  • 874
  • 182
  • @@casZ6
  • 966
  • button2121
  • CS21211211
  • 非同步
  • Vgi5xJ9n
  • IDENTITY
  • yyy
  • print[t]
  • cnBC7jyM
  • AD
  • 指令
  • json212112
  • FB
  • Chrome2121
  • hosts
  • ssL
  • -5137 UNIO
  • ef
  • https
  • ajax ORDER
  • DB21211211
  • 3614
  • -1797
  • web
  • Line[t]
  • -1068
  • -5815
  • [U2]
  • admin[t]
  • isnull
  • qe8ye3bj
  • Referrer
  • -2576 ORDE
  • en
  • 0.6avnc
  • sys_log
  • kerry
  • Rm8noQZH
  • Block
  • ordermain[
  • web.
頁數 1 / 7 下一頁
搜尋 ip[t] 結果:
Chrome 連接藍芽 BLE 印表機(出單機),使用 ESC / POS 列印繁體中文和 Qrcode
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>BLE Printer Test</title>
    <script src="https://cdn.jsdelivr.net/npm/iconv-lite-umd@0.6.10/lib/iconv-lite-umd.js"></script>
</head>
<body>
    <h1>BLE Printer Test 123</h1>
    <button id="printTestButton">Test Print</button>
    <button id="printAgain">Print Again</button>
    <pre id="log"></pre>
    <script>
        // Function to log messages on the page
        function logMessage(message) {
            const logElement = document.getElementById('log');
            logElement.textContent += message + '\n';
        }
        var device;
        var server;
        var service;
        var characteristic;
        var name = 'T58_6752'; // 藍芽設備的名稱
        var uuid = 0x1800; //service UUID (用 BLE Scanner 找到的)
        var characteristicUuid = 0x2A00; // characteristic UUID (用 BLE Scanner 找到的)
        // Function to connect to BLE printer and send test data

        async function connectAndTestPrint() {

            try {
                logMessage("Requesting Bluetooth device...");

                // Request the BLE device
                device = await navigator.bluetooth.requestDevice({
                    //acceptAllDevices: true,
                    filters: [
                        { name: name }
                    ],
                    optionalServices: [uuid] // Replace with the correct service UUID
                });

                logMessage(`Device selected: ${device.name}`);
                logMessage(`Device uuid: ${device.id}`);

                await printAgain();


            } catch (error) {
                logMessage(`Error: ${error.message}`);
            }
        }

        // 转码方法
        function stringToGbk(str) {
            const ranges = [
                [0xA1, 0xA9, 0xA1, 0xFE],
                [0xB0, 0xF7, 0xA1, 0xFE],
                [0x81, 0xA0, 0x40, 0xFE],
                [0xAA, 0xFE, 0x40, 0xA0],
                [0xA8, 0xA9, 0x40, 0xA0],
                [0xAA, 0xAF, 0xA1, 0xFE],
                [0xF8, 0xFE, 0xA1, 0xFE],
                [0xA1, 0xA7, 0x40, 0xA0],
            ]
            const codes = new Uint16Array(23940)
            let i = 0

            for (const [b1Begin, b1End, b2Begin, b2End] of ranges) {
                for (let b2 = b2Begin; b2 <= b2End; b2++) {
                    if (b2 !== 0x7F) {
                        for (let b1 = b1Begin; b1 <= b1End; b1++) {
                            codes[i++] = b2 << 8 | b1
                        }
                    }
                }
            }
            const cstr = new TextDecoder('gbk').decode(codes)

            // 编码表
            const table = new Uint16Array(65536)
            for (let i = 0; i < cstr.length; i++) {
                table[cstr.charCodeAt(i)] = codes[i]
            }
            const buf = new Uint8Array(str.length * 2)
            let n = 0

            for (let i = 0; i < str.length; i++) {
                const code = str.charCodeAt(i)
                if (code < 0x80) {
                    buf[n++] = code
                } else {
                    const gbk = table[code]
                    buf[n++] = gbk & 0xFF
                    buf[n++] = gbk >> 8
                }
            }
            u8buf = buf.subarray(0, n)
            // console.log(u8buf);
            return u8buf
        }

        async function printAgain() {
            // Connect to the GATT server
            server = await device.gatt.connect();
            logMessage("Connected to GATT server.");

            // Get the printer service
            service = await server.getPrimaryService(uuid); // Replace with your printer's service UUID
            logMessage("Printer service retrieved.");

            // Get the characteristic for writing data
            characteristic = await service.getCharacteristic(characteristicUuid); // Replace with the correct characteristic UUID
            logMessage("Printer characteristic retrieved.");

            // Prepare test print data
            const encoder = new TextEncoder();
            const testData = encoder.encode("TEST PRINT: Hello from Web Bluetooth!\n");
            const finalData = encoder.encode("--\n--\n \n \n");

            const setFontSize = new Uint8Array([0x1D, 0x21, 0x11]); // GS ! n
            const setFontSize2 = new Uint8Array([0x1D, 0x21, 0x22]); // GS ! n
            const setFontSize3 = new Uint8Array([0x1D, 0x21, 0x33]); // GS ! n

            // Write test data to the printer
            logMessage("Sending test data to printer...");

            await characteristic.writeValue(new Uint8Array([0x1D, 0x21, 0x00]));
            await characteristic.writeValue(encoder.encode("1x1!\n"));
            await characteristic.writeValue(setFontSize);
            await characteristic.writeValue(encoder.encode("2x2!\n"));
            await characteristic.writeValue(setFontSize2);
            await characteristic.writeValue(encoder.encode("3x3!\n"));
            await characteristic.writeValue(setFontSize3);
            await characteristic.writeValue(encoder.encode("4x4!\n"));


            const initPrinter = new Uint8Array([0x1B, 0x40]); // ESC @
            await characteristic.writeValue(initPrinter);

            // 3. 設置字符集為 GBK
            const setGBK = new Uint8Array([0x1B, 0x74, 0x11]); // ESC t 0x11 (GBK)
            await characteristic.writeValue(setGBK);

            const text = "繁體中文測試\n \n";
            const encodedText = stringToGbk(text);
            await characteristic.writeValue(encodedText);
            logMessage("Test data sent successfully!");

            // QrCode 列印
            const qrData = "https://example.com"; // Your QR code data
            const qrDataLength = qrData.length + 3;
            const pL = qrDataLength & 0xFF; // Low byte
            const pH = (qrDataLength >> 8) & 0xFF; // High byte

            const commands = [
                0x1B, 0x40, // Initialize printer
                0x1D, 0x28, 0x6B, pL, pH, 0x31, 0x50, 0x30, ...new TextEncoder().encode(qrData), // Store data
                0x1D, 0x28, 0x6B, 0x03, 0x00, 0x31, 0x51, 0x30 // Print QR code
            ];

            const buffer = new Uint8Array(commands);
            await characteristic.writeValue(buffer);
            logMessage("QrCode sent successfully!");

            await characteristic.writeValue(finalData);
            logMessage("finalData sent successfully!");

            // Disconnect the GATT server
            server.disconnect();
            logMessage("Disconnected from printer.");
        }

        // Bind the function to the button
        document.getElementById('printTestButton').addEventListener('click', connectAndTestPrint);
        document.getElementById('printAgain').addEventListener('click', printAgain);
    </script>
</body>
</html>
More...
Bike, 2025/1/2 下午 02:20:15
Orable table lock soluiton
當發現於Developer下Script時,ScriptRunner一直在跑都不會停的時候,請先暫停該Query。

1. 查出哪隻Session將Table lock住了
select b.owner,b.object_name,a.session_id,a.locked_mode from v$locked_object a,dba_objects b where b.object_id = a.object_id;

2. 取得該Session SID與SERIAL#,順便可以看是哪個SQL帳號Lock的
select b.username,b.sid,b.serial#,logon_time from v$locked_object a,v$session b where a.session_id = b.sid order by b.logon_time;

3. 使用下面這段SQL,帶入SID與SERIAL#即可將該Session Kill
alter system kill session'{SID}, {SERIAL#}';
More...
梨子, 2022/10/26 下午 08:35:31
使用Lucene.Net達成全文檢索!基礎解說(二)
上一集當中我們完成了Lucene基本操作中的Create與Read,這一集會將CRUD中的Update與Delete的操作方法告訴你,並且本集會著重於講解關於"Norms"與權重(Boost)在Lucene中的運作概念。

首先我們建立一個.Net 6的主控台應用程式
   

建立好後於右側專案右鍵選擇"管理Nuget套件",並選擇"瀏覽">於搜索列中搜尋"Lucene">安裝3.0.3最新穩定版 與 "System.Configuration.ConfigurationManager"

 
 安裝好後就可以於專案內使用Lucene套件囉!
再來依照上一篇的教學建立一套簡單的Lucene查詢

using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.QueryParsers;
using Lucene.Net.Search;
using Lucene.Net.Store;

var _dir = new DirectoryInfo("LuceneDocument");
if (!File.Exists(_dir.FullName))
{
    System.IO.Directory.CreateDirectory(_dir.FullName);
}
var analyzer = new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_CURRENT);
CreateIndex(GetProductsInformation(), _dir, analyzer);

while (true)
{
    Console.Write("請輸入欲查詢字串 :");
    var searchValue = Console.ReadLine();
    Search(searchValue, _dir, analyzer);
}

void CreateIndex(List<Product> information, DirectoryInfo dir, StandardAnalyzer analyzer)
{
    using (var directory = FSDirectory.Open(dir))
    {
        using (var indexWriter = new IndexWriter(directory, analyzer, true, IndexWriter.MaxFieldLength.LIMITED))
        {
            foreach (var index in information)
            {
                var document = new Document();
                document.Add(new Field("Id", index.Id.ToString(), Field.Store.YES, Field.Index.NO));
                document.Add(new Field("Name", index.Name, Field.Store.YES, Field.Index.ANALYZED));
                document.Add(new Field("Description", index.Description, Field.Store.YES, Field.Index.ANALYZED));
                indexWriter.AddDocument(document);
            }
            indexWriter.Optimize();
            indexWriter.Commit();
        }
    }
}
void Search(string searchValue, DirectoryInfo dir, StandardAnalyzer analyzer)
{
    using (var directory = FSDirectory.Open(_dir))
    {
        var parser = new QueryParser(Lucene.Net.Util.Version.LUCENE_CURRENT, "Description", analyzer).Parse(searchValue);
        using (var indexSearcher = new IndexSearcher(directory))
        {
            var queryLimit = 20;
            var hits = indexSearcher.Search(parser, queryLimit);
            if (!hits.ScoreDocs.Any())
            {
                Console.WriteLine("查無相關結果。");
                return;
            }
            Document doc;
            foreach (var hit in hits.ScoreDocs)
            {
                doc = indexSearcher.Doc(hit.Doc);
                Console.WriteLine("Score :" + hit.Score + ", Id :" + doc.Get("Id") + ", Name :" + doc.Get("Name") + ", Description :" + doc.Get("Description"));
            }
        }
    }
}

List<Product> GetProductsInformation()
{
    return new List<Product> {
            new Product{ Id = 1, Name = "蘋果", Description = "一天一蘋果,醫生遠離我。"},
            new Product{ Id = 2, Name = "橘子", Description = "醫生給娜美最珍貴的寶藏。"},
            new Product{ Id = 3, Name = "梨子", Description = "我是梨子,比蘋果好吃多囉!"},
            new Product{ Id = 4, Name = "葡萄", Description = "吃葡萄不吐葡萄皮,不吃葡萄倒吐葡萄皮"},
            new Product{ Id = 5, Name = "榴槤", Description = "水果界的珍寶!好吃一直吃。"}
        };
}
class Product
{
    public long Id { get; set; }
    public string Name { get; set; } = null!;
    public string Description { get; set; } = null!;
}


好囉! 接下來我們要如何更新索引呢?
更新其實就是將存在的索引刪除並重新建立Document,不存在的則直接新增。
首先準備一組資料準備更新

List<Product> GetUpdateProductsInformation()
{
    return new List<Product>
    {
        new Product{ Id = 6, Name = "香蕉", Description = "運動完後吃根香蕉補充養分。"},
        new Product{ Id = 2, Name = "橘子", Description = "橘子跟柳丁你分得出來嗎?"}
    };
}


*欲更新的Document必須與創建所引時使用的Document欄位相同*

void Update(string key, List<Product> information, DirectoryInfo dir, StandardAnalyzer analyzer)
{
    using( var directory = FSDirectory.Open(dir))
    {
        using(var indexWriter = new IndexWriter(directory, analyzer, false, IndexWriter.MaxFieldLength.LIMITED))
        {
            foreach (var index in information)
            {
                var document = new Document();
                document.Add(new Field("Id", index.Id.ToString(), Field.Store.YES, Field.Index.NO));
                document.Add(new Field("Name", index.Name, Field.Store.YES, Field.Index.ANALYZED));
                document.Add(new Field("Description", index.Description, Field.Store.YES, Field.Index.ANALYZED));
                indexWriter.UpdateDocument(new Term("Name", key) ,document);
            }
        }
    }
}


來測試看看
   
可以看見 Name = 橘子 的索引已經改為我們新準備的資料囉。
再來是刪除!
與更新非常相似,只需要使用deleteDocument()就可以了。

void Delete(string key, DirectoryInfo dir, StandardAnalyzer analyzer)
{
    using (var directory = FSDirectory.Open(dir))
    {
        using (var indexWriter = new IndexWriter(directory, analyzer, false, IndexWriter.MaxFieldLength.LIMITED))
        {
            indexWriter.DeleteDocuments(new Term("Name", key));
            indexWriter.Optimize();
            indexWriter.Commit();
        }
    }
}


再來看看輸出結果
 可以發現 Score :0.7554128, Id :2, Name :橘子, Description :醫生給娜美最珍貴的寶藏。這筆索引已經被移除囉!
  
可以發現筆者於更新或刪除時都是輸入單一字來做異動,除了表達可以對索引做複合更動外,
是因為更新與刪除索引同樣會使用到分詞器(analyzer),
*所輸入的索引值非ID等數值時必須要配合分詞器的分詞能力*才能取得所想異動的索引喔!


Boost是什麼呢?
Boost 分為 :
1. Index Time Boost : 在建立索引時就計算好的值。例如上一篇中提到的(NORMS)
2. Query Time Boost : 查詢時賦與搜尋條件不同的值以影響結果。
我們先來測試Index Time Boost的部分
void CreateIndexWithBoost(List<Product> information, DirectoryInfo dir, StandardAnalyzer analyzer)
{
    using (var directory = FSDirectory.Open(dir))
    {
        using (var indexWriter = new IndexWriter(directory, analyzer, true, IndexWriter.MaxFieldLength.LIMITED))
        {
            foreach (var index in information)
            {
                var document = new Document();
                document.Add(new Field("Id", index.Id.ToString(), Field.Store.YES, Field.Index.NO));
                document.Add(new Field("Name", index.Name, Field.Store.YES, Field.Index.ANALYZED));
                document.Add(new Field("Description", index.Description, Field.Store.YES, Field.Index.ANALYZED));
                document.GetField("Name").Boost = 1.5F;
                document.GetField("Description").Boost = 0.5F;

                indexWriter.AddDocument(document);
            }
            indexWriter.Optimize();
            indexWriter.Commit();
        }
    }
}


並記得重新CreateIndex才能刷新欄位的權重值喔。


很明顯的搜尋出來的Score分數變動了! 但是有沒有發現明明Name欄位的Boost改成了1.5,蘋果的數值卻仍然只有一半呢?
這是因為我們的Search中所參照的欄位為Description,所以在計算Score的時候其實是完全沒有參與的喔!
另外要記得,使用Index Time Boost的時候,欲給予銓重分配的欄位Field.Index不能使用NO_NORMS,不然這個欄位並不會紀錄權重的資料。

再來我們試試看Query Time Boost
void SearchWithBoost(string searchValue, DirectoryInfo dir, StandardAnalyzer analyzer)
{
    using (var directory = FSDirectory.Open(_dir))
    {
        using (var indexSearcher = new IndexSearcher(directory))
        {
            var query = new QueryParser(Lucene.Net.Util.Version.LUCENE_CURRENT, "Name", analyzer).Parse(searchValue);
            var query2 = new QueryParser(Lucene.Net.Util.Version.LUCENE_CURRENT, "Description", analyzer).Parse(searchValue);

            query.Boost = 2.0F;
            query2.Boost = 0.5F;

            BooleanQuery booleanQuery = new BooleanQuery();
            booleanQuery.Add(query, Occur.SHOULD);
            booleanQuery.Add(query2, Occur.SHOULD);

            var hits = indexSearcher.Search(booleanQuery, 20);
            if (!hits.ScoreDocs.Any())
            {
                Console.WriteLine("查無相關結果。");
                return;
            }
            Document doc;
            foreach (var hit in hits.ScoreDocs)
            {
                doc = indexSearcher.Doc(hit.Doc);
                Console.WriteLine("Score :" + hit.Score + ", Id :" + doc.Get("Id") + ", Name :" + doc.Get("Name") + ", Description :" + doc.Get("Description"));
            }
        }
    }
}


這次我們搜尋兩個欄位"Name"與"Description",並使用 BooleanQuery來將其組合。
BooleanQuery中的 Occur有三種參數 : "MUST","MUST_NOT","SHOULD",功能與字面上的意思一樣為"必須要有","必須沒有"與"有無都包含"。

 
查詢出來的分數就不一樣囉!

以上就是這一次的分享,Lucene是一款容易入門但是要實際上戰場卻又十分複雜的功能,想要達成真正高效能的全文檢索,在前期的文件規畫配置與資料的權重配比都是一個巨大的挑戰。未來會繼續分享關於Lucene的其他有趣功能,還請繼續期待呦!
另外也可以到GitHub下載我的範例來參考呦!
GitHub: https://github.com/g13579112000/Lucene

參考文件:
1. 黑暗大大的全文檢索筆記 : https://blog.darkthread.net/blog/lucene-net-notes-1/
2. Makble : http://makble.com/lucene-field-boost-example
3. CSDN Jack2013tong 文章 : https://blog.csdn.net/huwei2003/article/details/53408388
More...
梨子, 2022/4/20 下午 09:34:03
使用Lucene.Net達成全文檢索!基礎解說(一)
Lucene.Net是一套C#開源全文索引庫,其主要包含了:
· Index : 提供索引的管理與詞組的排序
· Search : 提供查詢相關功能
· Store : 支援資料儲存管理,包括I/O操作
· Util : 共用套件
· Documents : 負責描述索引儲存時的文件結構管理
· QueryParsers : 提供查詢語法
· Analysis : 負責分析內容
要達到高效能的全文檢索讓機器可以明白我們的語言,最重要的關鍵就是"分詞器"了。
試想一下這一句話你會如何拆分成一段一段的關鍵字呢?
"一天一蘋果,醫生遠離我"
還有英文版本
"An apple a day, doctor keep me away."
中文版本的拆分:
"一天"、"一"、"蘋果"、"醫生"、"遠離"、"我"
英文版本的拆分:
"apple"、"day"、"doctor"、"keep"、"me"、"away"
有沒有注意到不同語系所分析出來的關鍵字有一點不一樣呢?
而在Lucene中分詞的工作會交給Analysis來完成,
不過我們可以依照不同的語系去選擇想使用的分詞器(Analyzer)!

首先簡單說明一下Lucene的實作流程
1. 確認主要搜尋的語系來決定使用的分詞器(analyzer)
2. 建立Document依照analyzer匯入資料
(前置完成)
3. 建立IndexSearcher導入準備好的Document
4. 建立Parser來分析SearchValue
5. 使用IndexSearcher分析Parser取得結果(Hits)
*本專案使用的是Lucene.Net 3.0.3*
接下來我們來建立一個提供查詢使用的Document。

       
 // 取得或建立Lucene文件資料夾
        if (!File.Exists(_dir.FullName))
        {
            System.IO.Directory.CreateDirectory(_dir.FullName);
        }
        // Asp.Net Core需要於Nuget安裝System.Configuration.ConfigurationManager提供用戶端應用程式的組態檔存取
        Lucene.Net.Store.Directory directory = FSDirectory.Open(_dir);
        // 選擇分詞器
        var analyzer = new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_CURRENT);
        // 資料來源
        var repository = new Repository();
        // 依照指定的文件結構來建立
        var indexWriter = new IndexWriter(directory, analyzer, true, IndexWriter.MaxFieldLength.LIMITED);
        foreach (var index in repository)
        {
            var document = new Document();
            document.Add(new Field("Id", index.Id.ToString(), Field.Store.YES, Field.Index.NO));
            document.Add(new Field("Name", index.Name, Field.Store.YES, Field.Index.ANALYZED));
            document.Add(new Field("Description", index.Description, Field.Store.NO, Field.Index.ANALYZED));
            indexWriter.AddDocument(document);
        }
        indexWriter.Optimize();
        indexWriter.Commit();
        indexWriter.Dispose();


如此一來我們就建立好Lucene的基本配備囉!
其中analyzer的部分我們使用Lucene.Net預設,
要特別注意的是,其處理中文語系的能力非常之爛!
之後再寫一篇文章深入探討。
再來值得一提的是
       
document.Add(new Field("Id", index.Id.ToString(), Field.Store.YES, Field.Index.NO));

前兩個參數就是Key跟Value,可以簡單理解為欄位與其內容。
後面兩個參數是重點!
Store: 代表是否儲存這個Key的Value
例如在google打上台南美食會搜索出許多不同的文章連結,
不過google給你的資料中最重要的不是文章內容(Description),
而是哪一篇文章(Name)與台南美食最有關係。
假如今天我只要回傳一個列表而不用提示文章中有哪些內容,
那麼我就可以選擇給"Description" Field.Store.No來節省空間。
Index:
· NO - 不加入索引,這個內容只需要隨著結果出爐,不需要在查詢的時候被考慮。
· ANALYZED、NOT_ANALYZED - 是否使用分詞
· NO_NORMS - 關閉權重功能
或許許多人會對權重功能(NORMS)感到疑惑,
簡單的舉個例子
{ Id=1, Key="蘋果", Value="一天一蘋果,醫生遠離我。"}
{ Id=2, Key="橘子", Value="醫生給娜美最珍貴的寶藏。"}
{ Id=3, Key="梨子", Value="我是梨子,比蘋果蘋果好吃多囉!"}
當我搜尋"蘋果"的時候結果會是
{ Id=1, MatchKey=1, MatchValue=1, Score=(1*5) + (1*2) = 7}
{ Id=3, MatchKey=0, MatchValue=1, Score=(0*5) + (2*2) = 4}
有發現了嗎?
雖然同樣都對中兩個結果但是Id 1的資料Key值中有包含關鍵字,
因此得到較高的分數排在Id 3前方
準備好Document了,我們可以開始來實際使用看看囉!

       
// 決定所要搜索的欄位
        var parser = new QueryParser(Lucene.Net.Util.Version.LUCENE_CURRENT, "Description", analyzer).Parse(searchValue);
        // 提供剛剛建立的Document
        var indexSearcher = new IndexSearcher(directory);
        // 搜尋取出結果的數量
        var queryLimit = 20;
        // 開始搜尋!
        var hits = indexSearcher.Search(parser, queryLimit);
        if (!hits.ScoreDocs.Any())
        {
            Console.WriteLine("查無相關結果。");
            return;
        }
        Document doc;
        foreach (var hit in hits.ScoreDocs)
        {
            doc = indexSearcher.Doc(hit.Doc);
            Console.WriteLine("Score :" + hit.Score + ", Id :" + doc.Get("Id") + ", Name :" + doc.Get("Name") + ", Description :" + doc.Get("Description"));
        }


最後的結果(Hits),是需要再回到Document去撈出對應的資料喔!
是不是非常簡單呢?
筆者寫了一個簡單的範例在GitHub上,秉持著追求新技術的心使用了.Net 6,還請各位大大多多包涵。
有中英文兩種Repository,只需要在上方的DI注入切換就可以囉!
GitHub連結: https://github.com/g13579112000/Lucene
筆者第一次撰寫這種教學文章,有哪邊錯誤的非常歡迎一起來討論指教。
之後有機會再撰寫Lucene更深入的應用方面,
例如權重的分配與分詞器的選擇與使用。
感謝您的閱讀。


參考文獻:
1.黑暗大大的全文檢索筆記: https://blog.darkthread.net/blog/lucene-net-notes-1/
2.使用.Net實現全文檢索: https://blog.csdn.net/huwei2003/article/details/53408388
3.伊凡的部落格: http://irfen.me/5-lucene4-9-learning-record-lucene-analysis-tokenizer/
4.純淨天空代碼範例: https://vimsky.com/zh-tw/examples/detail/csharp-ex-Lucene.Net.Documents-Document---class.html
More...
梨子, 2022/2/24 下午 08:23:46
前端處理網址組網址工具
在後端部分,因為有uw元件,在處理url時已經很好處理了
但是在JavaScript因為我們沒有固定js在使用,
因此提供一套簡易使用網址處理工具
thisPage.ParameterByName(key) //取得網址上特定參數
thisPage.OriUrl(key) //中間一段提供修改變數的功能


    <script>
        var thisPage = {
            Init: function () {
                thisPage.InitPageInput();

                $("body")
                    ;
                thisPage.ChangeEvent();
            },
            ParameterByName: function (targetKey) {
                var res = null;
                const urlSearchParams = new URLSearchParams(window.location.search);
                const params = Object.fromEntries(urlSearchParams.entries());
                for (const [key, value] of Object.entries(params)) {
                    if (targetKey.trim().toLocaleLowerCase() === key) {
                        res = value;
                    }
                }
                return res;
            },
            OriUrl: function () {
                var arrayUrl = [];
                arrayUrl.push(window.location.protocol);//https:
                arrayUrl.push("//");
                arrayUrl.push(window.location.hostname);//blog.uwinfo.com.tw
                if (window.location.port.length > 0) {
                    //大多情況,不用特別指定port
                    arrayUrl.push(":");
                    arrayUrl.push(window.location.port);//80
                }
                arrayUrl.push(window.location.pathname);//post/Edit.aspx
                //換一套寫法
                //arrayUrl.push(window.location.search);//?Id=321
                const urlSearchParams = new URLSearchParams(window.location.search);
                const params = Object.fromEntries(urlSearchParams.entries());
                var ayyarQueryString = [];
                //這邊可以加工增加額外的key值
                for (const [key, value] of Object.entries(params)) {
                    if (value.trim().length > 0) {
                        //這邊要注意中文需要encode
                        ayyarQueryString.push(key + "=" + encodeURIComponent(value));
                    }
                }
                if (ayyarQueryString.length > 0) {
                    arrayUrl.push("?");
                    arrayUrl.push(ayyarQueryString.join('&'));
                }
                return arrayUrl.length > 0 ? arrayUrl.join('') : '';
            },
            InitPageInput: function () {
                const urlSearchParams = new URLSearchParams(window.location.search);
                const params = Object.fromEntries(urlSearchParams.entries());
                for (const [key, value] of Object.entries(params)) {
                    $('input[name=' + key + ']').val(value);
                    //這邊因為input有多種不同輸入方式,可以自行編輯
                    //$('select[name=' + key + ']').val(value);
                    //$('textarea[name=' + key + ']').html(value);
                }
            },
            ChangeEvent: function () {

            },
        }
        $(function () {
            thisPage.Init();
        });
    </script>
More...
Doug, 2021/10/1 下午 12:19:30
衝碼問題 許功蓋 Big5 ASCII
衝碼問題[編輯]
因為低位元字元中包含了程式語言、shell、script中,字串或命令常會用到的特殊字元,例如0x5C「\」、0x7C「|」等。「\」在許多用途的字串中是當作轉義符號,例如\n(換行)、\r(歸位)、\t(tab)、\\(\本身符號)、\"(引號)等等。而「|」在UNIX作業系統中大多當作命令管線的使用,如"ls -la | more"等等。如果在字串中有這些特殊的轉義字元,會被程式或直譯器解釋為特殊用途。但是因為是中文的原因,故無法正確解釋為上面所述的行為,因此程式可能會忽略此轉義符號或是誤當作轉義符號而中斷執行。若此,就違反了使用者本來要當成中文字元一部份使用的本意。[11]
低位元字元與ASCII重疊的字元如下︰
@ A-Z [ \ ] ^ _ ` a-z { | } ~
在常用字如「功」(0xA55C)、「許」(0xB35C)、「蓋」(0xBB5C)、「育」(0xA87C)中時常出現,造成了許多軟體無法正確處理以Big5編碼的字串或檔案。這個問題被戲謔性地人名化,稱為「許功蓋」或「許蓋功」(這三個字都有這種問題)。
一般的解決方法,是額外增加「\」的字元,因為「\\」會被解釋為「\」,所以「成功\因素」這個字串就能無誤地被程式當作「成功因素」的字串來處理。但是額外的困擾是,有些輸出功能並不會把「\」當作特殊字元看待,所以有些程式或網頁就會錯誤地常常出現在「許功蓋」這些字後面多了「\」。[11]

參考資料
zh.wikipedia.org/wiki/大五碼
More...
sean, 2020/12/30 下午 03:00:46
命名規則

名命規則

C#: 

    參數, 區域變數: 小駝峰(CamelCasing)
    其它: 大駝峰(PascalCasing)
    參考:
    
https://docs.microsoft.com/zh-tw/dotnet/standard/design-guidelines/naming-guidelines

Javascript:
    小駝峰(CamelCasing)


網址:
    全小寫, 用 - (減號) 分隔單字
    參考:

    https://www.seoseo.com.tw/article_detail_609.html
    https://blog.miniasp.com/post/2011/01/14/Avoid-using-underline-as-domain-name-character
    http://epaper.gotop.com.tw/pdf/acn023600.pdf


class 名命 HTML : 
    全小寫, 用 - (減號) 分隔單字 

複合字範列:
Pascal Camel Not
BitFlag bitFlag Bitflag
Callback callback CallBack
Canceled canceled Cancelled
DoNot doNot Don't
Email email EMail
Endpoint endpoint EndPoint
FileName fileName Filename
Gridline gridline GridLine
Hashtable hashtable HashTable
Id id ID
Indexes indexes Indices
LogOff logOff LogOut
LogOn logOn LogIn
Metadata metadata MetaData, metaData
Multipanel multipanel MultiPanel
Multiview multiview MultiView
Namespace namespace NameSpace
Ok ok OK
Pi pi PI
Placeholder placeholder PlaceHolder
SignIn signIn SignOn
SignOut signOut SignOff
UserName userName Username
WhiteSpace whiteSpace Whitespace
Writable writable Writeable
DateTimePicker dateTimePicker DatetimePicker
More...
Bike, 2020/7/28 上午 08:00:08
在 CentOS 上面啟動多個 .Net Core 的網站
1. 參考這裡設定第一個網站:
https://blog.johnwu.cc/article/centos-asp-net-core-neginx.html
注意, 文章中有一個錯誤:
/etc/nginx/conf.d/my-website.conf 的第 27 行, 應該是 include /etc/nginx/conf.d/default_proxy_settings;

2.  因為要避開 5000 port, 所以修改第二個網站的 appsettings.json, 讓第二個網站開在 5002 port, 如下.
{
"Logging": {
    "LogLevel": {
     "Default": "Information",
     "Microsoft": "Warning",
     "Microsoft.Hosting.Lifetime": "Information"
    }
},
"Kestrel": {
        "EndPoints": {
                "Http": {
                        "Url": "http://localhost:5002"
                }
        }
},
"AllowedHosts": "*"
}


3. 新增 /etc/nginx/conf.d/my-website2.conf, 要注意
    A. portal2
    B. server localhost:5002
    C. server_name coretest2.bike.idv.tw
    當然 SSL 憑証的檔名也要記得改.

upstream portal2 {
    # localhost:5000 改成 ASP.NET Core 所監聽的 Port
    server localhost:5002;
}

server {
    # 只要是透過這些 Domain 連 HTTP 80 Port,都會轉送封包到 ASP.NET Core
    listen 80;
    # 可透過空白區分,綁定多個 Domain
    server_name coretest2.bike.idv.tw;
    location / {
        proxy_pass http://portal2/;
        include /etc/nginx/conf.d/default_proxy_settings;
    }
}

# 用 HTTPS 必須要有 SSL 憑證,如果沒有要綁定 SSL 可以把下面整段移除
server {
    # 只要是透過這些 Domain 連 HTTPS 443 Port,都會轉送封包到 ASP.NET Core
    listen 443 ssl;
    server_name coretest2.bike.idv.tw;
    ssl_certificate /etc/nginx/ssl/coretest2.bike.idv.tw.crt;
    ssl_certificate_key /etc/nginx/ssl/coretest2.bike.idv.tw.key;

    location / {
        proxy_pass http://portal2/;
        include /etc/nginx/conf.d/default_proxy_settings;
    }
}


改完後就可以用了.

以下是 https://blog.johnwu.cc/article/centos-asp-net-core-neginx.html 抄過來的一些檔案, 作為備份:

setup-aspnet-core.sh
#!/bin/bash

main() {
    sudo yum -y install epel-release
    sudo yum -y update

    install_nginx
    install_dotnet

    sudo firewall-cmd --add-service=http --permanent
    sudo firewall-cmd --add-service=https --permanent
    sudo firewall-cmd --reload
}

install_nginx() {
    echo "###################################"
    echo "########## Install Nginx ##########"
    echo "###################################"
    sudo yum -y install httpd-tools nginx
    sudo setsebool -P httpd_can_network_connect on
    sudo sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config
    sudo setenforce 0
    sudo systemctl enable nginx
    sudo systemctl restart nginx
}

install_dotnet() {
    echo "###########################################"
    echo "########## Install .NET Core 2.2 ##########"
    echo "###########################################"
    sudo rpm -Uvh https://packages.microsoft.com/config/rhel/7/packages-microsoft-prod.rpm
    sudo yum -y install aspnetcore-runtime-2.2
}

main "$@"


用以下指令執行:
sudo sh setup-aspnet-core.sh



/etc/systemd/system/my-website.service, (/bin/dotnet 有可能是 /usr/bin/dotnet)
[Unit]
# Description=<此服務的摘要說明>
Description=MyWebsite

[Service]
# WorkingDirectory=<ASP.NET Core 專案目錄>
WorkingDirectory=/usr/share/my-website

# ExecStart=/bin/dotnet <ASP.NET Core 起始 dll>
ExecStart=/bin/dotnet MyWebsite.dll

# 啟動若失敗,就重啟到成功為止
Restart=always
# 重啟的間隔秒數
RestartSec=10

# 設定環境變數,注入給 ASP.NET Core 用
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false

[Install]
WantedBy=multi-user.target


服務相關指令:
# 開啟,開機自動啟動服務
systemctl enable my-website.service

# 關閉,開機自動啟動服務
systemctl disable my-website.service

# 啟動服務
systemctl start my-website.service

# 重啟服務
systemctl restart my-website.service

# 停止服務
systemctl stop my-website.service

# 查看服務狀態
systemctl status my-website.service


/etc/nginx/conf.d/default_proxy_settings
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;


/etc/nginx/conf.d/my-website.conf
upstream portal {
    # localhost:5000 改成 ASP.NET Core 所監聽的 Port
    server localhost:5000;
}

server {
    # 只要是透過這些 Domain 連 HTTP 80 Port,都會轉送封包到 ASP.NET Core
    listen 80;
    # 可透過空白區分,綁定多個 Domain
    server_name demo.johnwu.cc example.johnwu.cc;
    location / {
        proxy_pass http://portal/;
        include /etc/nginx/conf.d/default_proxy_settings;
    }
}

# 用 HTTPS 必須要有 SSL 憑證,如果沒有要綁定 SSL 可以把下面整段移除
server {
    # 只要是透過這些 Domain 連 HTTPS 443 Port,都會轉送封包到 ASP.NET Core
    listen 443 ssl;
    server_name demo.johnwu.cc;
    ssl_certificate /etc/nginx/ssl/demo.johnwu.cc_bundle.crt;
    ssl_certificate_key /etc/nginx/ssl/demo.johnwu.cc.key;

    location / {
        proxy_pass http://portal/;
        include /etc/nginx/conf.d/default_proxy_settings;
    }
}


Nginx 重新啟動:
# 檢查 Nginx 的設定是否有誤
nginx -t

# 若沒有錯誤,即可套用
nginx -s reload
More...
Bike, 2020/7/7 上午 08:01:43
把 yyyymmdd 轉換成 yyyy-mm-dd 的神奇 javascript
試試這個:
"20190101".replace(/(\d{4})(\d{2})(\d{2})/g, '$1-$2-$3')

取得 yyyyMMdd 的方法
var D = new Date();
D.toISOString().slice(0,10).replace(/-/g,"");
More...
Bike, 2019/8/21 下午 04:47:55
上傳圖片 直的變成橫的的問題
https://stackoverflow.com/questions/17186568/upload-from-ios-picture-to-net-app-rotate

 讀取某個參數來判斷            if (image_file.PropertyIdList.Contains(0x0112))
            {
                int rotationValue = image_file.GetPropertyItem(0x0112).Value[0];
                switch (rotationValue)
                {
                    case 1: // landscape, do nothing
                        break;

                    case 8: // rotated 90 right
                            // de-rotate:
                        image_file.RotateFlip(rotateFlipType: RotateFlipType.Rotate270FlipNone);
                        break;

                    case 3: // bottoms up
                        image_file.RotateFlip(rotateFlipType: RotateFlipType.Rotate180FlipNone);
                        break;

                    case 6: // rotated 90 left
                        image_file.RotateFlip(rotateFlipType: RotateFlipType.Rotate90FlipNone);

                        image_file.Save(Path + FileName);
                        break;
                }
            }
More...
sean, 2019/8/19 下午 06:11:28
|< 1234567 >|
頁數 1 / 7 下一頁
~ Uwinfo ~