tag:blogger.com,1999:blog-59465307047421309702024-03-06T16:20:07.273+08:00Jax 的工作紀錄除了在整理學習上的經驗,同時也能幫助其他需要的人Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.comBlogger39125tag:blogger.com,1999:blog-5946530704742130970.post-87252133296736508052022-09-08T13:39:00.005+08:002022-09-08T14:31:26.385+08:00架構解釋<p>以前在專案開發時對分層架構所定下的原則,去避免不必要的地雷,以及每一層的要處裡的職權。</p>
<p>我會考慮分層架構,是希望提高系統的嚴謹度,以及 Method 的共用性,所以 Dao 層級就要避免功能導向,不然就是把單頁的程式拆分到多個層去而已。</p>
<p> </p>
<h3>DomainModel / ViewModel</h3>
<ul>
<li>這三類都屬於 <a href="https://en.wikipedia.org/wiki/Plain_Old_CLR_Object" target="_blank">POCO</a> 類型的物件,單純的資料載體,不允許有動作的 method,不可以外部取資料</li>
<li>相同資料的欄位命名必須一致,反例: ProjectId, Pid, pid, PID, p_id</li>
<li>有欄位就要有資料,明明有欄位定義卻不從 DB 取資料,這會造成不必要的雷</li>
</ul>
<p> </p>
<h3>DomainModel</h3>
<ul>
<li>欄位定義可與 DB 一致</li>
<li>將 DB 的資料進行彙整,成為完整的資料體</li>
</ul>
<p> </p>
<h3>ViewModel</h3>
<p>欄位定義與 View 的表單(Form)一致,與 DomainModel 可能很相似,但有些欄位只會用在表單上,所以 DomainModel 不適合用在表單 Binding 上</p>
<p>例如:</p>
<ul>
<li>同意上述條款</li>
<li>舊密碼, 新密碼, 確認密碼</li>
</ul>
<p> </p>
<h3>Dao</h3>
<ul>
<li>回傳 DomainModel</li>
<li>定義單純的 BD 操作 List, GetById, Save, Insert, Update</li>
<li>與 ORM 不同,是進一步將資料操作簡化</li>
<li>保證 Method 的通用性,避免功能導向,例如: 有一個 Method 是專門為了A畫面功能而存在的</li>
</ul>
<p> </p>
<h3>Service</h3>
<ul>
<li>回傳 DomainModel</li>
<li>驗證商業邏輯,如必要資料欄位,數值範圍等...</li>
<li>調用一個或多個 Dao 來完成商業邏輯</li>
<li>處理 DB 交易,來協調多個 Dao 的調用</li>
</ul>
<p> </p>
<h3>Controller</h3>
<ul>
<li>處理 DomainModel 到 ViewModel 的轉換</li>
<li>調用 Service 進行 DomainModel 的資料處理</li>
<li>調配 Responses 的結果 Html, Json, PDF ...</li>
<li>驗證資料類型的正確,數值﹑日期</li>
<li>負責 Access 的權限阻擋</li>
</ul>
<p> </p>
<h3>View</h3>
<ul>
<li>View 是被動的,不能存取任何 Controller / Service / Dao</li>
<li>負責資料呈現及格式化,如 日期﹑金額 ...</li>
<li>Ajax 只能對 Controller 調用</li>
</ul>
<p> </p>
<h3>附註</h3>
<ol>
<li>同一層之間不可以互相參考,這容易發生循環參考,例如:
<ul>
<li>Controller 呼叫 Controller</li>
<li>Service 呼叫 Service</li>
<li>Dao 呼叫 Dao</li>
</ul>
</li>
<li>同一層之間有相同邏輯可以抽離到 Support 類<br/> </li>
<li>建議定義命名規範,讓每一個人寫出來的程式像是同一個人寫的,好處是降低支援或接手的人的困難度<br/> </li>
<li>任何第三方的 API 都需要包裝,隔離直接相依的問題,而且可以單獨測試<br/> </li>
</ol>
<p> </p>
<h3>架構關係全貌</h3>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVkwcFPiMgYAI77ojQ-7uiud0enzdxVAexakLwozTveWuPeCfjXD5ABdCw6a1r1Xv1rWD8oM5HLET8aaukooy0xSfVK1skfASa4tPVjdEHdigEa20J7nO5FpdYBH_JpNv6169QIKlZGHNE8tIRoKS4KzzQ1_9ghgsrEu1V82nOZ_aKlL5U_DsPjmATGw/s698/%E6%9E%B6%E6%A7%8B%E8%A7%A3%E9%87%8BA.png"><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVkwcFPiMgYAI77ojQ-7uiud0enzdxVAexakLwozTveWuPeCfjXD5ABdCw6a1r1Xv1rWD8oM5HLET8aaukooy0xSfVK1skfASa4tPVjdEHdigEa20J7nO5FpdYBH_JpNv6169QIKlZGHNE8tIRoKS4KzzQ1_9ghgsrEu1V82nOZ_aKlL5U_DsPjmATGw/s600/%E6%9E%B6%E6%A7%8B%E8%A7%A3%E9%87%8BA.png"/></a>
<p> </p>
<h3>Model 傳遞的關係</h3>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitPjeZp31dPOetYZOsq06OnuPtNdTKaiek3PHNsmBmQpgTP7Ik9agnwDVoL8lRme5mIV59Ct4jtfuSKVbQY9TVjHUX2qx0ZFeJCw0-aOCt0rI0qJ-hVFNKG1RrhHhC73BTGSMU9XqfKPaLK0f5eKEUuvSXBVoKC2KzGuKSKdFc5RH_8cj3wCsZ2M5oqQ/s528/%E6%9E%B6%E6%A7%8B%E8%A7%A3%E9%87%8BB.png"><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitPjeZp31dPOetYZOsq06OnuPtNdTKaiek3PHNsmBmQpgTP7Ik9agnwDVoL8lRme5mIV59Ct4jtfuSKVbQY9TVjHUX2qx0ZFeJCw0-aOCt0rI0qJ-hVFNKG1RrhHhC73BTGSMU9XqfKPaLK0f5eKEUuvSXBVoKC2KzGuKSKdFc5RH_8cj3wCsZ2M5oqQ/s600/%E6%9E%B6%E6%A7%8B%E8%A7%A3%E9%87%8BB.png"/></a>
<p> </p>
<h3>用 Interface 隔離實作</h3>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0AU8ww03wJDlVHcK9J6YcsM8g4cfxYNE31p8IXYg9F90pcvNb8AMbldXM5U1HCEwoERTPSKIQySFBurA7aJGJkS7x9DS8b1k3jCl1vYaHl3W6JeQ9LmshMkyvXPHsiNAW94clEiUJtA1XNv1K2vNAcpgsXTiZfb1r98yjt_sH9aGA_JosNsxwKABJ1g/s586/%E6%9E%B6%E6%A7%8B%E8%A7%A3%E9%87%8BC.png"><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0AU8ww03wJDlVHcK9J6YcsM8g4cfxYNE31p8IXYg9F90pcvNb8AMbldXM5U1HCEwoERTPSKIQySFBurA7aJGJkS7x9DS8b1k3jCl1vYaHl3W6JeQ9LmshMkyvXPHsiNAW94clEiUJtA1XNv1K2vNAcpgsXTiZfb1r98yjt_sH9aGA_JosNsxwKABJ1g/s600/%E6%9E%B6%E6%A7%8B%E8%A7%A3%E9%87%8BC.png"/></a>
<p> </p>
<h3>Web Api 的呼叫關係</h3>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrenh3MuEHl1cjacIDy0VVFUBbxczmwdmEEc61TuGbDTc2C56bqjM3puRj1q9iC32yK-Dt_yKI0xje0Gw7Cu9tn4KI_Ddv4tv_uAqnjVEJ09qDUkwPm6q1w1YTtEf_kL_G8AzD6bK3IOgwnvikHr1_7SOZnZU4uRhtpTBdvNtWx3ybYeTN-1hp4ZIPtg/s430/%E6%9E%B6%E6%A7%8B%E8%A7%A3%E9%87%8BD.png"><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrenh3MuEHl1cjacIDy0VVFUBbxczmwdmEEc61TuGbDTc2C56bqjM3puRj1q9iC32yK-Dt_yKI0xje0Gw7Cu9tn4KI_Ddv4tv_uAqnjVEJ09qDUkwPm6q1w1YTtEf_kL_G8AzD6bK3IOgwnvikHr1_7SOZnZU4uRhtpTBdvNtWx3ybYeTN-1hp4ZIPtg/s600/%E6%9E%B6%E6%A7%8B%E8%A7%A3%E9%87%8BD.png"/></a>
<p> </p>
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-62018283885626750192021-10-16T23:40:00.003+08:002023-02-25T18:14:07.492+08:00C# MVC 的 UserException 攔截處理<p>為了將錯誤訊息呈現給使用者,要完成幾件事:</p>
<ul>
<li>用 Filter 攔截 UserException</li>
<li>將錯誤訊息存到 TempData,之後呈現在畫面上</li>
<li>回到原本的畫面,需要產生 ViewResult</li>
<li>針對 Ajax 請求用 400 狀態回應,並將訊息放在 http content</li>
</ul>
<br/>
<br/>
<h3>.Net Framework MVC</h3>
<pre class="cs" name="code">
using System;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web.Mvc;
namespace XXXXX.Mvc.Filters
{
/// <summary>例外訊息過濾器</summary>
public class ExceptionMessageActionFilter : ActionFilterAttribute
{
/// <summary></summary>
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
/* 自動將 Action 的 Parameters 中的 ViewModel 賦予給 ViewData
* 不然要自己在 Action 的第一行寫 ViewData.Model = domain;
*/
string paramName = filterContext.ActionDescriptor.GetParameters()
.OrderBy(x => Regex.IsMatch(x.ParameterType.ToString(), @"(ViewModel|Domain)\b") ? 0 : 1)
.Select(x => x.ParameterName)
.FirstOrDefault();
if (paramName != null)
{ filterContext.Controller.ViewData.Model = filterContext.ActionParameters[paramName]; }
}
/// <summary></summary>
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
/* 不處理子 Action */
if (filterContext.IsChildAction) { return; }
/* 判斷 Exception 是否已經被處理了 */
if (filterContext.ExceptionHandled) { return; }
/* 只針對 UserException 進行錯誤處理*/
var ex = filterContext.Exception as UserException;
if (ex == null) { return; }
/* 標記 Exception 已經被處理了,讓後續的 Filter 不用再處理 */
filterContext.ExceptionHandled = true;
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
/* 對 Ajax 請求的處理 */
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
filterContext.HttpContext.Response.StatusCode = 400;
filterContext.Result = new ContentResult { Content = ex.Message };
}
else if (ex is JwNoDataException)
{
/* 資料不存在的處理 */
filterContext.Controller.TempData["StatusError"] = ex.Message;
filterContext.Result = new HttpNotFoundResult("[" + ex.Message + "]");
}
else
{
/* 一般畫面的處理 */
filterContext.Controller.TempData["StatusError"] = ex.Message;
filterContext.Result = new ViewResult
{
ViewData = filterContext.Controller.ViewData,
TempData = filterContext.Controller.TempData
};
}
}
}
}
</pre>
<p>在 FilterConfig.cs 配置</p>
<pre class="cs" name="code">
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
//...
filters.Add(new ExceptionMessageActionFilter());
//...
}
}
</pre>
<br/>
<br/>
<h3>Net core 3.1 MVC</h3>
<pre class="cs" name="code">
using System;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Orion.Mvc.Filters
{
/// <summary>例外訊息過濾器</summary>
public class ExceptionMessageActionFilter : ActionFilterAttribute
{
/// <summary></summary>
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
/* 自動將 Action 的 Arguments 中的 ViewModel 賦予給 ViewData
* 不然要自己在 Action 的第一行寫 ViewData.Model = domain;
*/
var arguments = filterContext.ActionArguments.Values.Where(x => x != null);
object model = arguments
.OrderBy(x => Regex.IsMatch(x.GetType().Name, @"(ViewModel|Domain)\b") ? 0 : 1)
.FirstOrDefault();
var controller = filterContext.Controller as Controller;
controller.ViewData.Model = model;
}
/// <summary></summary>
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
/* 判斷 Exception 是否已經被處理了 */
base.OnActionExecuted(filterContext);
if (filterContext.ExceptionHandled) { return; }
/* 只針對 UserException 進行錯誤處理*/
var ex = filterContext.Exception as UserException;
if (ex == null) { return; }
/* 標記 Exception 已經被處理了,讓後續的 Filter 不用再處理 */
filterContext.ExceptionHandled = true;
var controller = filterContext.Controller as Controller;
var headers = filterContext.HttpContext.Request.Headers;
bool isAjax = headers["X-Requested-With"] == "XMLHttpRequest";
if (isAjax)
{
/* 對 Ajax 請求的處理 */
filterContext.HttpContext.Response.StatusCode = 400;
filterContext.Result = new ContentResult { StatusCode = 400, Content = ex.Message };
}
else if (ex is UserNoDataException)
{
/* 資料不存在的處理 */
controller.TempData["StatusError"] = ex.Message;
filterContext.HttpContext.Response.StatusCode = 404;
}
else
{
/* 一般畫面的處理 */
controller.TempData["StatusError"] = ex.Message;
filterContext.Result = new ViewResult
{
ViewData = controller.ViewData,
TempData = controller.TempData
};
}
}
}
}
</pre>
<p>在 Startup.cs 配置</p>
<pre class="cs" name="code">
public void ConfigureServices(IServiceCollection services)
{
//...
IMvcBuilder mvcBuilder = services
.AddMvc(options =>
{
//...
options.Filters.Add(new ExceptionMessageActionFilter());
//...
})
.AddControllersAsServices()
;
//...
}
</pre>
<br/>
<br/>
<h3>Net core 3.1 Razor Page</h3>
<pre class="cs" name="code">
using System;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace XXXXX.Api.Filters
{
public class ExceptionMessagePageFilter : AbstractPageFilter
{
public override void OnPageHandlerExecuted(PageHandlerExecutedContext context)
{
if (context.ExceptionHandled) { return; }
/* 判斷是否有指定的 Exception */
var ex = context.Exception;
if (ex is UserException userEx) { handleUserException(context, userEx); return; }
if (ex is HttpException httpEx) { handleHttpException(context, httpEx); return; }
}
private void handleUserException(PageHandlerExecutedContext context, UserException ex)
{
/* 只針對 PageModel 進行錯誤處理*/
var page = context.HandlerInstance as PageModel;
if (page == null) { return; }
/* 標記 Exception 已經被處理了,讓後續的 Filter 不用再處理 */
context.ExceptionHandled = true;
var headers = filterContext.HttpContext.Request.Headers;
bool isAjax = headers["X-Requested-With"] == "XMLHttpRequest";
if (isAjax)
{
/* 對 Ajax 請求的處理 */
context.HttpContext.Response.StatusCode = 400;
context.Result = new ContentResult
{
StatusCode = 400,
Content = ex.Message
};
}
else if (ex is UserNoDataException)
{
/* 資料不存在的處理 */
page.TempData["StatusError"] = ex.Message;
context.HttpContext.Response.StatusCode = 404;
}
else
{
/* 一般畫面的處理 */
page.TempData["StatusError"] = ex.Message;
context.Result = page.Page();
}
}
private void handleHttpException(PageHandlerExecutedContext context, HttpException ex)
{
context.ExceptionHandled = true;
var headers = context.HttpContext.Request.Headers;
bool isAjax = headers["X-Requested-With"] == "XMLHttpRequest";
if (isAjax)
{
context.HttpContext.Response.StatusCode = ex.StatusCode;
context.Result = new ContentResult
{
StatusCode = ex.StatusCode,
Content = ex.Message,
};
}
else
{
context.HttpContext.Response.StatusCode = ex.StatusCode;
context.Result = new StatusCodeResult(ex.StatusCode);
}
}
}
}
</pre>
<p>在 Startup.cs 配置</p>
<pre class="cs" name="code">
public void ConfigureServices(IServiceCollection services)
{
//...
IMvcBuilder mvcBuilder = services
.AddRazorPages(options =>
{
//...
})
.AddMvcOptions(options =>
{
//...
options.Filters.Add(new ExceptionMessagePageFilter());
//...
})
;
//...
}
</pre>
<br/>
<br/>
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-77170394557555981952021-10-16T23:09:00.001+08:002021-10-16T23:09:33.443+08:00用 Exception 作為使用者錯誤訊息的通道<p>自訂一個 UserException 來傳遞給使用者的訊息,然後程式裡只要簡單的 throw 訊息,接著就是在全域的錯誤處理中將訊息呈現給使用者。</p>
<p>這是一種非常方便的方式,因為可以無視 method 的階層深度,在程式的任何地方都可以將訊息傳到使用者面前。</p>
<p>原則上盡可能將錯誤轉化成 UserException,並且錯誤訊息要能讓使用者<code>[明確知道]</code>要如何正確操作軟體,但是只有工程師才可以修正的錯誤就應該記錄下來,並給使用者這樣的訊息<code>[系統出現錯誤,請通知管理者]</code>,只有盡可能的修正錯誤才不會讓使用者常常打電話吵你。</p>
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-84635376874706696232021-10-16T11:51:00.001+08:002023-02-25T18:16:19.751+08:00重構基礎-邏輯反相<p>利用程式具有區段的能力,我們可以將 <code>[如果 A 成立就做 B]</code> 轉換成 <code>[如果 A 不成立就離開]</code>,用這個方式做邏輯反相可以讓原本看似複雜的程式簡化很多,讓程式變得清晰思慮也會變的清晰,困難的問題就變得簡單,或著就不在是問題了。</p>
<p>這是一段重構前的程式:</p>
<pre class="cs" name="code">
foreach (var item in list)
{
Guid guid = new Guid(item.project_GUID.Value.ToString());
var project = _dc.Project.FirstOrDefault(w => w.ProjectID == guid);
if (project != null)
{
var mails = !String.IsNullOrEmpty(project.AlertEmail) ? project.AlertEmail.Split(new char[]{';'}) : null;
if (mails != null)
{
if(!mails.Any(x => x.mail == "a@b.c"))
{
foreach (var mail in mails)
{
if (!mailDic.ContainsKey(mail.Trim()))
mailDic.Add(mail, new List<int>());
mailDic[mail].Add(item.projectID);
}
}
}
}
}
</pre>
<br/>
<p>將邏輯反相重構後的程式:</p>
<pre class="cs" name="code">
foreach (var item in list)
{
Guid guid = new Guid(item.project_GUID.Value.ToString());
var project = _dc.Project.FirstOrDefault(w => w.ProjectID == guid);
if (project == null) { continue; }
if (String.IsNullOrEmpty(project.AlertEmail)) { continue; }
var mails = project.AlertEmail.Split(new char[]{';'});
if(mails.Any(x => x.mail == "a@b.c")){ continue; }
foreach (var mail in mails)
{
if (!mailDic.ContainsKey(mail.Trim()))
{ mailDic.Add(mail, new List<int>()); }
mailDic[mail].Add(item.projectID);
}
}
</pre>
<br/>
<p>可以看出程式的縮排階層減少了,也簡化了程式的複雜度,重構的方法就是將 if 反相將 else 前移,也許一開始很難用這種方式寫程式,但是人腦是可以訓練的,在每次重構時進行調整改寫,習慣這種模式後很自然就會用這種<b>[跳離]</b>的思維去寫程式邏輯了。</p>
<p>當然這種方式也是有缺點的,就是有時候不這麼直覺會不好理解,但是可以增加一些註解來輔助描述,或是拆分判斷條件例如:</p>
<pre class="cs" name="code">
if (a != null || b == null) { return; }
/* 這可以拆分成兩行,不用拘泥一行完成 */
if (a != null) { return; }
if (b == null) { return; }
</pre>
<br/>
<p>程式邏輯有一個有趣的事,往往用正向邏輯無法處理的問題,改用反相邏輯就會簡單很多,例如:需要用特定的邏輯尋找符合的項目,可能因為那個特定邏輯讓效能非常的差,這時候如果改用不符合特定邏輯的項目排除,留下來的就會是符合的項目,因為是用消去法所以要尋找的數量會越算越少,所以執行效能會收斂。</p>
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-75816354379789685932021-10-15T17:26:00.004+08:002023-02-25T18:17:08.463+08:00全域的 Exception 處理<p>大部分具有 Exception 機制的程式語言都有提供全域的 Exception 處理,如果你已經有用 log 去記錄錯誤的習慣的話,與其在程式裡佈滿了 try catch,不如在全域處裡中去記錄沒有被 catch 的 Exception,這樣程式就可以更乾淨了,而且程式如果發生異常的關閉時也會進入全域處裡,可以確保 Exception 妥善地被記錄。</p>
<br/>
<h3>Console, WinForm 程式</h3>
<pre class="cs" name="code">
using System;
using System.Security.Permissions;
using System.Threading;
using System.Windows.Forms;
using NLog;
namespace AppThreadException
{
static class Program
{
private static readonly ILogger _log = LogManager.GetCurrentClassLogger();
[STAThread]
[SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.ControlAppDomain)]
public static void Main(string[] args)
{
/* ThreadException 用來攔截 UI 錯誤 */
Application.ThreadException += threadExceptionHandler;
/* UnhandledException 只能攔截錯誤,不能阻止程式關閉 */
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
AppDomain.CurrentDomain.UnhandledException += unhandledExceptionHandler;
Application.Run(new MyForm());
}
/// <summary>攔截 UI 錯誤</summary>
private static void threadExceptionHandler(object sender, ThreadExceptionEventArgs e)
{
_log.Fatal(e.Exception, "操作錯誤");
MessageBox.Show(e.Exception.Message, "操作錯誤", MessageBoxButtons.OK, MessageBoxIcon.Stop);
}
/// <summary>攔截不可挽回的錯誤,不能阻止程式關閉</summary>
private static void unhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e)
{
Exception ex = (Exception)e.ExceptionObject;
_log.Fatal(ex, "執行錯誤");
MessageBox.Show(ex.Message, "執行錯誤", MessageBoxButtons.OK, MessageBoxIcon.Stop);
}
}
}
</pre>
<br/>
<h3>Web 程式</h3>
<p>在 Global.asax.cs 中可以設定 Application_Error 就可以攔截未處裡的 Exception,除了用這個方法記錄錯誤,還可以用現有的套件(<a href="https://elmah.github.io/a/mvc/" target="_blank">elmah</a>)幫我們完成。</p>
<pre class="cs" name="code">
using System;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using NLog;
namespace MvcReport
{
public class MvcApplication : System.Web.HttpApplication
{
private static readonly ILogger _log = LogManager.GetCurrentClassLogger();
//...
protected void Application_Error(object sender, EventArgs e)
{
Exception ex = Server.GetLastError();
_log.Fatal(ex, ex.Message);
}
//...
}
}
</pre>
<br/>
<h3>Net core 程式</h3>
<p>Net core 的程式都是相同的方式,Web Request 的要另外配置</p>
<pre class="cs" name="code">
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Autofac.Extensions.DependencyInjection;
using EverTop.Api;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using NLog;
namespace MyWebApp
{
public class Program
{
private static readonly ILogger _log = LogManager.GetCurrentClassLogger();
public static void Main(string[] args)
{
/* UnhandledException 只能攔截錯誤,不能阻止程式關閉 */
AppDomain.CurrentDomain.UnhandledException += unhandledExceptionHandler;
/* 用來攔截 Task 錯誤 */
TaskScheduler.UnobservedTaskException += unobservedTaskException;
var host = Host.CreateDefaultBuilder(args)
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureWebHostDefaults(webHostBuilder => webHostBuilder
.UseStartup<Startup>()
)
.Build();
host.Run(); /* 啟動網站 */
}
private static void unhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e)
{
Exception ex = (Exception)e.ExceptionObject;
_log.Fatal(ex, "執行錯誤");
}
private static void unobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
_log.Fatal(e.Exception, "執行錯誤");
e.SetObserved();
}
}
}
</pre>
<br/>
<h3>Net core 3.1 Web 程式</h3>
<p>在 Startup.cs 中配置 middleware 進行 Exception 攔截</p>
<pre class="cs" name="code">
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (_env.IsProduction())
{
app.UseExceptionHandler("/Error");
}
else
{
app.UseDeveloperExceptionPage();
}
app.UseStatusCodePagesWithReExecute("/Error");
/* 利用 middleware 進行 Exception 攔截 */
/* 這裡的順序很重要,不然會被前面 ExceptionHandler 處理掉就拿不到 Exception */
app.Use(async (context, next) =>
{
try
{
await next();
}
catch (Exception ex)
{
_log.Fatal(ex, "執行錯誤");
throw; /* 把 Exception 再丟出去給別人處理 */
}
});
}
</pre>
<br/>
<h3>週期性 Thread 的處裡方式</h3>
<p>將主要邏輯寫在另外 method 裡,這樣可以專注在 Exception 上。</p>
<pre class="cs" name="code">
private bool _runFlag = false;
public void Start()
{
if (_runFlag) { return; }
_runFlag = true;
var thread = new Thread(() =>
{
while (_runFlag)
{
try
{
cycleHandler();
}
catch (Exception ex)
{
_log.Fatal(ex, "執行錯誤");
}
Thread.Sleep(1000);
}
});
thread.Start();
}
public void Stop()
{
_runFlag = false;
}
private void cycleHandler()
{
// 主要的邏輯程式寫在這裡
}
</pre>
<br/>
<h3>PHP 錯誤處理</h3>
<p>Ref: <a href="https://www.php.net/manual/zh/function.set-error-handler.php" target="_blank">set_error_handler</a>, <a href="https://www.php.net/manual/zh/function.set-exception-handler.php" target="_blank">set_exception_handler</a></p>
<pre class="php" name="code">
<?php
function error_handler($errno, $errstr, $errfile, $errline) {
if(error_reporting() === 0){ return; }
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}
set_error_handler('error_handler', E_ALL^E_NOTICE);
function exception_handler($exception) {
// 在這記錄 log
throw $exception;
}
set_exception_handler('exception_handler');
throw new Exception('Uncaught Exception');
//$a = 1 / 0;
</pre>
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-7058641818976980032021-10-14T17:21:00.002+08:002023-02-25T18:19:29.839+08:00Exception 是傳遞訊息的通道<pre class="cs:nogutter:nocontrols" name="code">
void show(string tag)
{
Console.WriteLine(tag);
}
void methodA()
{
throw new Exception("error msg");
show("A");
}
void methodB()
{
methodA();
show("B");
}
void methodC()
{
try
{
methodB();
show("C");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
</pre>
<p>這裡為了方便我們理解將程式展開成下面的樣子:</p>
<pre class="cs:nogutter:nocontrols" name="code">
void show(string tag)
{
Console.WriteLine(tag);
}
void methodC()
{
try
{
methodB();
void methodB()
{
methodA();
void methodA()
{
throw new Exception("error msg");
//-----------------------------------------------------
show("A");
}
show("B");
}
show("C");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
</pre>
<p>從範例中我們可以看出兩個特性:</p>
<br/>
<h3>* 通透性</h3>
<p>從 throw 的那一行開始 Exception 會一直向上傳遞,直到 catch 的區段。</p>
<br/>
<h3>* 脫離性</h3>
<p>從 throw 的那一行之後的程式都不會被執行,會有類似 return 的效果,不同的是會對每一層的 method 都進行脫離,包含 try 的區段也會脫離。</p>
<br/>
<p>methodC 的 catch 會捕獲從 methodA 丟出的 Exception,並且 Exception 中的 StackTrace 會紀錄 methodA, methodB, methodC,而 show() 都不會被執行。</p>
<p>由於大部分的程式邏輯都可以轉化成 else 就離開的程式流程,正好符合 throw 的脫離性,所以我們一開始在撰寫程式邏輯的時候,可以先專注在一般正常的流程邏輯上,之後再去補足檢查邏輯或脫離邏輯,最後在合適的位置進行 try catch 處理,或者在全域的錯誤處理中進行處理。</p>
<br/>
<p>這裡有一個糟糕的 method,他用了個 Result 物件來裝載回傳結果以及錯誤訊息:</p>
<pre class="cs:nogutter:nocontrols" name="code">
public class Result
{
public int Value { get; set; }
public string Error { get; set; }
}
public Result BadMethod(int input)
{
var result = new Result();
if (input > 0)
{
result.Value = 100 / input;
}
else
{
result.Error = "input 需要大於零";
}
return result;
}
</pre>
<p>如果用 Exception 可以讓程式簡單很多:</p>
<pre class="cs:nogutter:nocontrols" name="code">
public int GoodMethod(int input)
{
if (input <= 0)
{
throw new Exception("input 需要大於零");
}
return 100 / input;
}
</pre>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-34143836736278381222021-10-13T16:45:00.007+08:002023-02-25T18:20:10.075+08:00初學 Exception 的疑問<p>一開始學習 Exception 一定會有很多疑問,或者根本沒搞清楚就寫了一堆 try catch,讓程式邏輯變得亂七八糟難以閱讀,甚至小小的修改都不知道從何下手,Exception 是一種邏輯的輔助工具,可以讓你事半功倍,也可以變成噩夢。</p>
<p>我也是摸索了很久才對 Exception 有一定的應用認識,不敢說有多專精,畢竟每種語言跟特性都不同,但也是一定程度的應用經驗,我認為最重要的用途有兩個:</p>
<br/>
<h3>* 用 finally 去關閉資源物件</h3>
<p>資源物件有 File, Socket,...,這類的物件在邏輯結束後一定要進行關閉,強調一定一定,實在是吃了自己埋的雷了,沒有關閉的話資源會被咬住,像 Serial Port 這種底層資源沒有關閉的話,想要重新連接就只能重開機了,這是讓人無法接受的。</p>
<pre class="cs:nogutter:nocontrols" name="code">
FileStream file1 = null;
try
{
file1 = File.Open("<file path>", FileMode.OpenOrCreate);
// Do something
}
finally
{
if (file1 != null) { file1.Close(); }
}
/*== C# 可以用 using 代替上面的程式 ==*/
using (FileStream file2 = File.Open("<file path>", FileMode.OpenOrCreate))
{
// Do something
}
</pre>
<br/>
<br/>
<h3>* 用 catch 去記錄 Exception</h3>
<p>為了日後可以在日後除錯時留下線索,Exception 的記錄是很重要的,任何程式都有可能出現錯誤,不管是資料錯誤還是邏輯錯誤,有除錯的線索肯定會有很大的幫助,無論程式的大小都應該留下錯誤紀錄。</p>
<pre class="cs:nogutter:nocontrols" name="code">
try
{
// Do something
}
catch (Expression ex)
{
_log.Error(ex, "An error occurred.");
}
</pre>
<br/>
<p>其餘的清況,在不知道為何要用 try catch 時,最好就不要寫 try catch,基本上程式邏輯一旦出錯就只能退出,如果胡亂地進行修正讓邏輯繼續下去,可能會出現很糟糕的局面,而且資料錯誤就應該修正資料,邏輯錯誤就應該修正邏輯。</p>
<p>錯誤就是要讓人發現才有辦法修正,胡亂的將錯誤屏蔽掉只會埋下未來的苦果,也別用這種方式報復前東家,這只會搞到後面接手的倒楣鬼,本是同根生,相煎何太急。</p>
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-46709306733624221822020-08-04T19:09:00.001+08:002023-02-25T18:22:21.747+08:00利用 redirect 跳轉到 預設頁 或 預設查詢在網站開發有一些技巧可以增加後續的維護性,利用重導向來做預設內容的處裡,這樣可以統一集中的進行控制。<br />
<br />
預設頁面最常會因為業務的策略因素進行調整,這時候散落在各地的連結都要調整,費工又容易漏。<br />
<br />
預設查詢這個麻煩點在於參數會變動,散落在各地的連結一樣是個麻煩。<br />
<br />
<br />
但重導向這種方式也是有損失的,將會多一個 HTTP 請求,這對 UI 反應速度很要求的狀況來說,並不是一個好方法,可能就要改用統一網址管理來處理。<br />
<br />
<br />
利用 MVC 的 Index() 來控制預設頁面,Index 將不會有實體頁面,而是用來進行重導向。<br />
<pre class="cs" name="code">public class UserController : Controller
{
public ActionResult Index()
{
return RedirectToAction(nameof(List));
}
public ActionResult List(DateTime? date)
{
//...
return View();
}
}
</pre><br />
<br />
判斷 QueryString 為空時,進行預設查詢的重導向,以 QueryString 為判斷點的好處是有時候就是要查詢全部資料,這樣就不會被預設查詢卡到。<br />
<pre class="cs" name="code">public class UserController : Controller
{
public ActionResult List(DateTime? date)
{
if (Request.QueryString.Count == 0)
{
RedirectToAction(nameof(List), new
{
date = DateTime.Today.ToString("yyyy-MM-dd")
});
}
//...
return View();
}
}
</pre><br />
<br />
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-8444822696862903002020-07-31T20:01:00.002+08:002020-07-31T23:08:58.175+08:00資料庫設計原則以前跟同事一起訂下的資料庫設計原則,來減少一開始設計不好造成事後要進行大改的風險。<br />
<br />
實體關聯的設計是最需要謹慎的,這裡一旦與規格差太多,程式可能就要重寫。<br />
<br />
欄位名稱建議在系統中是唯一的,這樣可以減少 JOIN 時還需要換名稱,FK 使用跟 PK 一樣的名稱也可以增加維護性。<br />
<br />
<br />
<hr /><br />
<h3>資料庫設計流程</h3><br />
(Instance 層級)<br />
<ul><li>依照 Instance 建立 ER 圖,須清楚描述關聯與實體</li>
<li>審視並釐清實體對於規格的含蓋範圍</li>
</ul><br />
(Table 層級)<br />
<ul><li>依照釐清後的含蓋範圍,修正 ER 圖</li>
<li>審視並確認完整 ER圖</li>
</ul><br />
(Column 層級)<br />
<ul><li>定義各資料表欄位,建立 OrgTableSchema.sql</li>
<li>審視並確認完整 Schema</li>
<li>有問題重複上述步驟</li>
</ul><br />
(Develop 層級)<br />
<ul><li>維護 OrgTableSchema.sql</li>
<li>使用 DbSchemaTool 工具產生 Schema.sql</li>
<li>建立資料庫</li>
<li>修正 Dbml</li>
<li>更新 DB 專案</li>
</ul><br />
<br />
<h3>資料表與欄位定義</h3><ul><li>Id 是 identity 去尾的縮寫,所以 d 要小寫</li>
<li>No 改成 Num 會比較好,因為會跟 Yes, No 混淆</li>
<li>Id 是流水號,其他的建議用 Num 或 Code 表示</li>
<li>避免用單單字做欄位名,盡量用多單字 ( Ex. IssueType, CompanyCode, SettingId, CompanyName )</li>
<li>盡量用 流水號 或 Guid 這類無意義的 id 做 PK</li>
<li>除了多對多的中間表,PK 都必須是單一欄位</li>
<li>資料來源為 OptionSetting, 值為 OptionId 者,欄位名稱命名須加尾贅詞 Id,型態為 INT</li>
<li>資料來源為 Enum, 欄位型態則為 Nvarchar(N)</li>
<li>盡量避免資料來源為 bool 對應 bit,因為擴充性太低,資料來源改用 Enum 對應 Nvarchar(N)</li>
<li>歷史檔與主檔的差異</li>
<ul><li>主檔的 ModifiedBy ModifierdDate 為歷史檔的 CreatedBy CreatedDate,歷史檔無 ModifiedBy ModifierdDate</li>
<li>新增歷史檔的 PK , 主檔的 PK 設定為 FK</li>
<li>其餘欄位應該與主檔相同</li>
</ul><li>每一個 FK 需預設建立 IX,其餘調整於開發完成後,依照使用者回報進行調整</li>
<li>多對多的中間表如果超過 3 個附加欄位,必須用一般的方式設計</li>
</ul><br />
<br />
<h3>PK 必須使用 Guid 的情況</h3><ul><li>如果只保留短期資料,但會有大量新增或刪除(每天一萬筆新增)</li>
<li>如果有多系統都可以新增資料,最後要合流的狀況</li>
</ul><br />
<br />
<h3>設計 Table 的欄位順序</h3><ol><li>PK</li>
<li>FK [主檔]</li>
<li>FK [選項]</li>
<li>AK</li>
<li>其餘不重要的資料欄位</li>
<li>CreatedBy</li>
<li>CreatedDate</li>
<li>ModifiedBy</li>
<li>ModifierdDate</li>
</ol><br />
<br />
<h3>資料型態</h3><ul><li>文字 : NVARCHAR 長度為預定輸入的兩倍</li>
<li>日期 : DATETIMEOFFSET 具有時區紀錄(MSDN 建議)</li>
<li>時間 : TIME</li>
<li>整數 : INT</li>
<li>Enum : NVARCHAR (32)</li>
<li>Guid : UNIQUEIDENTIFIER</li>
<li>金錢 : MONEY</li>
<li>精確浮點數 : DECIMAL (18, 4)</li>
</ul><br />
<br />
<h3>鍵值規則</h3><ul><li>Table 規則 {ProjectName}_{TableName}<br />
Ex: WMS_Carrier</li>
<li>Foreign Key 規則 FK_{ProjectName}_{TableName}_{Columns}<br />
Ex: FK_WMS_CarrierMaterial_CarrierId</li>
<li>Unique 規則 AK_{ProjectName}_{TableName}_{Columns}<br />
Ex: FK_WMS_CarrierMaterial_CarrierId</li>
<li>Index 規則 IX_{ProjectName}_{TableName}_{Columns}<br />
Ex: IX_WMS_CarrierMaterial_CarrierId</li>
</ul><br />
<br />
<h3>縮寫解釋</h3><ul><li>PK: Primary Key</li>
<li>FK: Foreign Key</li>
<li>AK: Alternate Key</li>
<li>IX: IndeX</li>
<li>CK: ChecK</li>
<li>DF: DeFault</li>
</ul><br />
<br />
<h3>MSSQL 定序設定</h3><br />
定序 Chinese_Taiwan_Stroke_CS_AI<br />
<br />
_CS 區分大小寫<br />
_CI 不區分大小寫<br />
<br />
_AS 區分腔調 a != á<br />
_AI 不區分腔調<br />
<br />
_KS 區分日文假名字元<br />
_WS 區分全形與半形字元<br />
<br />
定序會影響查詢與唯一值的判定,例如不區分大小的定序在 WHERE 'abc' = 'ABC' 會是 true。<br />
<br />
定序選擇並沒有標準答案,我個人是採用區分大小的定序,如果規格是不區分的時候,再用程式轉大寫或小寫,這部分可以在 DAO 統一完成,雖然也會有遺漏的情況,但至少是可以由程式掌控,如果出現只有某些不區分,而大部分還是區分的時候,還是由程式掌控會比較好。<br />
<br />
<br />
<br />
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-87759850099603716812019-07-19T16:46:00.001+08:002023-02-25T21:26:28.262+08:00WCF IP Filter<pre class="xml" name="code"><!-- Web.config -->
<system.serviceModel>
<extensions>
<behaviorExtensions>
<add name="ipFilter" type="XXX.XXX.IpFilterElement, XXX.XXX" />
</behaviorExtensions>
</extensions>
<!-- .... -->
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<ipFilter allow="192.168.1.0/24, 127.0.0.1" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</pre><br />
<pre class="cs" name="code">using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Configuration;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Serialization;
using System.Security.Authentication;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using JustWin.API.Extensions;
public class IpFilterElement : BehaviorExtensionElement
{
[ConfigurationProperty("allow", IsRequired = true)]
public virtual string Allow
{
get { return this["allow"] as string; }
set { this["allow"] = value; }
}
public override Type BehaviorType
{
get { return typeof(IpFilterBehaviour); }
}
protected override object CreateBehavior()
{
return new IpFilterBehaviour(Allow);
}
}
public class IpFilterBehaviour : IDispatchMessageInspector, IServiceBehavior
{
private readonly List<IPAddressRange> _allowList;
public IpFilterBehaviour(string allow)
{
_allowList = allow.Split(',').Select(x => new IPAddressRange(x)).ToList();
}
void IServiceBehavior.Validate(ServiceDescription service, ServiceHostBase host)
{
}
void IServiceBehavior.AddBindingParameters(ServiceDescription service, ServiceHostBase host, Collection<ServiceEndpoint> endpoints, BindingParameterCollection parameters)
{
}
void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription service, ServiceHostBase host)
{
foreach (ChannelDispatcher dispatcher in host.ChannelDispatchers)
foreach (EndpointDispatcher endpoint in dispatcher.Endpoints)
{
endpoint.DispatchRuntime.MessageInspectors.Add(this);
}
}
object IDispatchMessageInspector.AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
var remoteEndpoint = request.Properties[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty;
var address = IPAddress.Parse(remoteEndpoint.Address);
if(_allowList.Any(x => x.IsMatch(address))) { return null; }
request = null;
return new AuthenticationException($"IP address ({remoteEndpoint.Address}) is not allowed.");
}
void IDispatchMessageInspector.BeforeSendReply(ref Message reply, object correlationState)
{
var ex = correlationState as Exception;
if (ex == null) { return; }
MessageFault messageFault = MessageFault.CreateFault(
new FaultCode("Sender"),
new FaultReason(ex.Message),
ex,
new NetDataContractSerializer()
);
reply = Message.CreateMessage(reply.Version, messageFault, null);
}
}
public class IPAddressRange
{
private readonly byte[] _rangeAddress;
private readonly byte[] _rangeMask;
public IPAddressRange(string ipAndMask)
{
string[] split = (ipAndMask + "/128").Split('/');
var ip = IPAddress.Parse(split[0].Trim());
int maskLength = int.Parse(split[1].Trim());
if (ip.AddressFamily == AddressFamily.InterNetwork) { maskLength += 96; }
_rangeMask = createMask(maskLength);
_rangeAddress = ip.MapToIPv6().GetAddressBytes()
.Select((x, i) => x & _rangeMask[i])
.Select(x => (byte)x)
.ToArray();
}
public bool IsMatch(IPAddress ip)
{
byte[] address = ip.MapToIPv6().GetAddressBytes();
for (int i = 0; i < 16; i++)
{
if ((address[i] & _rangeMask[i]) != _rangeAddress[i]) { return false; }
}
return true;
}
private byte[] createMask(int length)
{
var mask = new byte[16];
for (int i = 0; i < 16; i++)
{
mask[i] = 0xff;
if (length > -8) { length -= 8; }
if (length < 0) { mask[i] = (byte)(mask[i] << -length); }
}
return mask;
}
}
</pre>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-33163877699696296622014-03-05T23:38:00.001+08:002023-02-25T21:32:20.715+08:00利用 HTTP Status Codes 傳遞 Ajax 成功失敗的狀態一般處理 Ajax 回應時會傳送的資訊種類有:資料、成功訊息、錯誤訊息、失敗訊息以及處理狀態,傳遞的資訊種類並不一致,再加上除了資料之外,通常還希望能傳遞處理狀態,這種情況大部分會選擇是以 JSON 的方式傳遞這兩個訊息,以下是常見的幾種格式:<br />
<br />
<pre class="js" name="code">{ code: 1, msg: "OK" }
{ success: true, result: "data", errorMsg: "" }
{ status: 'success', result: [], errorMsg: "" }
//...
</pre><br />
但以執行狀態跟操作行為作一個歸納,可以區分以下幾種回傳結果:<br />
<table class="table_list" cellspacing="0" cellpadding="4" border="1"> <tr class="header">
<th>資料操作</th>
<th>HTTP Method</th>
<th>成功</th>
<th>錯誤/失敗</th>
</tr>
<tr>
<td>檢視(Read)</td>
<td>GET</td>
<td>資料</td>
<td>錯誤/失敗訊息</td>
</tr>
<tr>
<td>新增(Create)<br />
修改(Update)<br />
刪除(Delete)</td>
<td>POST</td>
<td>成功訊息</td>
<td>錯誤/失敗訊息</td>
</tr>
</table><br />
從上面的歸納可以看出規律性,接著只要有方法可以傳送處理的狀態,以及能夠區分資料的種類,其實就單純很多,而 HTTP Status Codes 就是用來傳遞 HTTP 的處理狀態,如果利用這個方式來傳遞自訂的處理狀態,這樣 HTTP Content 就可以很單純傳遞資料,讓資料格式不受限於 JSON,還可以使用其他格式(text, xml, html),而且 XMLHttpRequest 本身就有處理 HTTP Status Codes 的能力,而 jQuery.ajax 也有提供 error status 的處理,所以可以利用這個來定義狀態的處理,在 HTTP Status Codes 有幾個已經定義狀態,很適合用來回傳處理狀態的資訊:<br />
<br />
<table class="table_list" cellspacing="0" cellpadding="4" border="1"> <tr>
<th>400</th>
<td>Bad Request</td>
<td>錯誤的請求</td>
<td>適用在表單內容的錯誤,如必填欄位未填、Email 格式錯誤</td>
</tr>
<tr>
<th>403</th>
<td>Forbidden</td>
<td>沒有權限,被禁止的</td>
<td>適用在沒有登入或權限不足</td>
</tr>
<tr>
<th>500</th>
<td>Internal Server Error</td>
<td>內部服務器錯誤</td>
<td>適用在程式的錯誤</td>
</tr>
</table><br />
<br />
jQuery 接收資訊的範例<br />
<pre class="js" name="code">$.ajax({
type: "POST",
url: document.location,
success: function (data, textStatus, jqXHR) {
alert(data);
},
error: function (jqXHR, textStatus, errorThrown) {
alert(jqXHR.responseText);
}
});
</pre> <br />
<br />
PHP 傳遞錯誤訊息的範例 <br />
<pre class="php" name="code">if (php_sapi_name() == 'cgi'){
header("Status: 400 Bad Request");
}else{
header("HTTP/1.0 400 Bad Request");
}
exit("儲存失敗!!");
</pre><br />
<br />
C# MVC 傳遞錯誤訊息的範例 <br />
<pre class="cs" name="code">Response.TrySkipIisCustomErrors = true;
Response.StatusCode = 400;
return Content("儲存失敗!!");
</pre>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-35414923386735070402014-02-15T14:45:00.002+08:002023-02-25T21:34:02.982+08:00[C#] delegate 到 Lambda Expressions 語法演進一開始要看懂 Lambda Expressions 有點困難,下面會以演進方式來介紹如何做到語法省略。<br />
<br />
首先定義一個單參數的 delegate<br />
<pre class="cs:nogutter:nocontrols" name="code">delegate int Del(int x);
</pre><br />
以傳統 delegate 的語法來建構 delegate <br />
<pre class="cs:nogutter:nocontrols" name="code">Del a = delegate(int x) { return x + 2; };
</pre><br />
去掉 delegate 改成 Lambda 表示式<br />
<pre class="cs:nogutter:nocontrols" name="code">Del a = (int x) => { return x + 2; };
</pre><br />
由於大括號裡只有一句陳述式,而且是一個 return 的陳述式,所以可以省略大括號跟 return<br />
<pre class="cs:nogutter:nocontrols" name="code">Del a = (int x) => x + 2;
</pre><br />
在 delegate 已經有定義輸入參數的型別,所以在小括號裡的型別可以省略<br />
<pre class="cs:nogutter:nocontrols" name="code">Del a = (x) => x + 2;
</pre><br />
由於小括號裡面只有一個輸入參數,所以可以再進一步省略小括號<br />
<pre class="cs:nogutter:nocontrols" name="code">Del a = x => x + 2;
</pre><br />
參考來源:<br />
<a target="_blank" href="http://msdn.microsoft.com/zh-tw/library/bb397687(v=vs.100).aspx">Lambda 運算式 (C# 程式設計手冊)</a><br />
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com1tag:blogger.com,1999:blog-5946530704742130970.post-68927165407301395562014-02-13T23:54:00.000+08:002014-02-14T12:29:56.438+08:00HTTP GET 與 POST 的比較及使用時機<table class="table_list" cellspacing="0" cellpadding="4" border="1"><tr class="header"> <th> </th> <th>GET</th> <th>POST</th> </tr>
<tr> <td style="white-space:nowrap;">瀏覽器歷史紀錄</td> <td>參數都會紀錄,因為都是URL的一部分</td> <td>參數都不會紀錄</td> </tr>
<tr> <td style="white-space:nowrap;">加入書籤</td> <td>參數都會紀錄,因為都是URL的一部分</td> <td>參數都不會紀錄</td> </tr>
<tr> <td style="white-space:nowrap;">回上一頁/<br />
重新載入</td> <td>GET請求是重新執行,但被存儲在瀏覽器的快取,則不被重新提交到服務器</td> <td>數據將被重新提交(瀏覽器通常會警告使用者該數據將需要重新提交)</td> </tr>
<tr> <td style="white-space:nowrap;">編碼類型</td> <td>application/x-www-form-urlencoded</td> <td>multipart/form-data 或 application/x-www-form-urlencoded,使用多編碼的二進制數據<br />
</td> </tr>
<tr> <td style="white-space:nowrap;">參數大小限制</td> <td>受限於 QueryString 長度限制(不超過 2KB 是最保險的,有些瀏覽器可以允許多達 64KB)</td> <td>允許大量傳輸,包括上傳文件到服務器</td> </tr>
<tr> <td style="white-space:nowrap;">參數傳輸方式</td> <td>QueryString</td> <td>POST Data(message-body)</td> </tr>
<tr> <td style="white-space:nowrap;">安全性</td> <td>容易破解,因為參數是網址的一部分,所以它被紀錄在瀏覽器歷史記錄和明文服務器日誌</td> <td>比較難破解</td> </tr>
<tr> <td style="white-space:nowrap;">使用性</td> <td>不應該被使用在發送密碼或其他敏感信息上</td> <td>使用在發送密碼或其他敏感信息上</td> </tr>
<tr> <td style="white-space:nowrap;">能見度</td> <td>GET方法是對所有人可見(它會顯示在瀏覽器的地址欄)</td> <td>POST方法變量不顯示在URL中</td> </tr>
<tr> <td style="white-space:nowrap;">執行速度</td> <td>快,GET 比 POST 快 1.5 倍</td> <td>慢,POST 多出需要發送數據的步驟</td> </tr>
<tr> <td style="white-space:nowrap;">快取</td> <td>瀏覽器會依據網址來快取資料,不同的網址有不同的快取</td> <td>瀏覽器不會快取</td> </tr>
<tr> <td style="white-space:nowrap;">自動重送</td> <td>會,在回應過長時會重發請求,直到重試結束</td> <td>不會,一個請求發出後會一直等待回應</td> </tr>
<tr> <td style="white-space:nowrap;">適用行為</td> <td>檢視(Read)</td> <td>新增(Create)、修改(Update)、刪除(Delete)</td> </tr>
<tr> <td style="white-space:nowrap;">情況包括</td> <td>透過 <link href="">、<img src="">、<script src="">、<iframe src=""> 額外載入的 JavaScritp、CSS、圖片</td> <td>透過 <form method="post"> 以及 Ajax post 發送的請求</td> </tr>
</table><br />
<br />
GET 適合用在「<b>檢視(Read)</b>」的操作行為,由於檢視資料的操作會遠比資料異動來的更頻繁,所以需要更快的回應,而且有快取可以加快二次檢視,參數在網址上可以使每一個網址都代表一個網頁,在加入書籤的連結能夠返回對應的網頁,再者所需要的參數很少(例如:id=122&type=1),對於資料異動後快取沒更新的問題,可以在 QueryString 加上資料最後修改的時間戳記(例如:id=122&type=1&t=1392183597718)。<br />
<br />
<br />
POST 適合用在「<b>新增(Create)</b>、<b>修改(Update)</b>、<b>刪除(Delete)</b>」的操作行為,資料異動所需要傳送的參數很可能超過 QueryString 的限制,不適合用 GET 來處理資料異動的傳送,GET 在等待過久會重新發送請求,這會造成重複的請求,如果在新增儲存就多新增一筆資料,而 POST 在一個請求發出後會一直等待回應,這可以保障在傳送的過程中請求是唯一的,新增資料的請求如果被記錄在書籤或歷史紀錄中,使用者點擊連結網址就新增一筆資料,這真是一件恐怖的事,所以在資料異動上的請求必須使用 POST 來傳送。<br />
<br />
<br />
參考來源:<br />
<a target="_blank" href="http://www.diffen.com/difference/GET_%28HTTP%29_vs_POST_%28HTTP%29">GET vs POST - Difference and Comparison | Diffen</a><br />
<a target="_blank" href="http://www.w3schools.com/tags/ref_httpmethods.asp">HTTP Methods: GET vs. POST</a><br />
<a target="_blank" href="http://www.oncoding.cn/2009/ajax-get-post/">寻根究底:Ajax请求的GET与POST方式比较</a><br />
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com2tag:blogger.com,1999:blog-5946530704742130970.post-17679267041939195262014-02-10T23:43:00.001+08:002023-02-25T21:34:43.604+08:00[C#] 對 FirstOrDefault 新的認識FirstOrDefault 會依據最後的型別去決定 Default 時回傳的值,例如下面的範例:<br />
<pre class="cs" name="code">int? a = (new List<int>{2}).Select(x => x).FirstOrDefault();
// 2
int? b = (new List<int>{}).Select(x => x).FirstOrDefault();
// 0
int? c = (new List<int>{2}).Select(x => (int?)x).FirstOrDefault();
// 2
int? d = (new List<int>{}).Select(x => (int?)x).FirstOrDefault();
// null
</pre>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-68430560756383572742014-02-05T20:53:00.003+08:002023-02-25T21:35:02.492+08:00[C#] HttpUtility.ParseQueryString 的隱藏密技在使用 Request.QueryString 發現 ToString 會產生 URL 的 query 字串,嘗試用 NameValueCollection 的 ToString 卻不是產生 URL 的 query 字串,這一整個就很奇怪,明明都是 NameValueCollection 確有不一樣的結果,透過 Reflector 發現 Request.QueryString 的 instance 型別是一個 HttpValueCollection,想說可以直接 new HttpValueCollection 出來使用,但 HttpValueCollection 卻是 System.Web 的內部 Class,外部是無法直接 new 出來使用,還好在又發現 HttpUtility.ParseQueryString 回傳的 NameValueCollection 的 instance 是 HttpValueCollection 這個型別,所以可以透過 HttpUtility.ParseQueryString 來建立 HttpValueCollection。<br />
<br />
<pre class="cs" name="code">// using System.Web;
var qs1 = HttpUtility.ParseQueryString("id=5&type=1");
qs1.ToString(); // "id=5&type=1"
var qs2 = HttpUtility.ParseQueryString(String.Empty);
qs2["id"] = "11";
qs2["name"] = "Tom";
qs2.ToString(); // "id=11&name=Tom"
</pre><br />
<br />
<strong>HttpValueCollection 的簽名</strong><br />
<pre class="cs" name="code">[Serializable]
internal class HttpValueCollection : NameValueCollection
{
}
</pre><br />
<br />
<strong>ParseQueryString 的簽名</strong><br />
<pre class="cs" name="code">public static NameValueCollection ParseQueryString(string query, Encoding encoding)
{
if (query == null)
{
throw new ArgumentNullException("query");
}
if (encoding == null)
{
throw new ArgumentNullException("encoding");
}
if ((query.Length > 0) && (query[0] == '?'))
{
query = query.Substring(1);
}
return new HttpValueCollection(query, false, true, encoding);
}
</pre>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-56047174653778322932014-02-05T20:25:00.000+08:002014-02-05T20:25:35.858+08:00[CSS] float 與 clear<h3>float</h3>一開始是用來定義文繞圖的呈現,後來其浮動特性很適合用來排版佈局,所以大部分的網頁都用這種方式排版。 <br />
<br />
特性:<br />
<ul><li>不佔用父元素的空間</li>
<li>寬高會內縮至子元素所呈現的大小</li>
<li>會排擠其他相鄰的 inline 元素</li>
<li>在所定位的空間不足時會自動換行</li>
</ul><br />
<pre class="xml:nogutter:nocontrols" name="code"><div style="border:1px solid #f00; float:left;">div 1</div>
<div style="background:#0f0;">div 2</div>
</pre><div style="border:1px solid #f00; float:left;">div1</div><div style="background:#0f0;">div2</div><br />
透過上面的範例可以看出因為 div1 不佔用空間而讓 div2 的位子上移了,然而呈現與 div1 重疊的效果,以及可以看到 float 排擠文字的特性,而讓 div2 的文字被擠到 div1 之後。<br />
<br />
<br />
<br />
<h3>clear</h3>清除在元素相鄰邊上的 float 元素<br />
<br />
屬性:<br />
<ul><li>none 不做任何 clear 動作</li>
<li>left 將元素向下換行,來排除具有 float:left 的相鄰元素</li>
<li>right 將元素向下換行,來排除具有 float:right 的相鄰元素</li>
<li>both 將元素向下換行,來排除具有 float:left 或 float:right 的相鄰元素</li>
</ul><br />
<pre class="xml:nogutter:nocontrols" name="code"><div style="border:1px solid #f00; float:left;">div1</div>
<div style="background:#0f0; clear:left;">div2</div>
</pre><div style="border:1px solid #f00; float:left;">div1</div><div style="background:#0f0; clear:left;">div2</div><br />
這個範例在 div2 加上 clear:left 的屬性,讓 div2 根據前一個具有 float:left 元素之後向下換行,來排除具有 float:left 的相鄰元素。<br />
<br />
<br />
<pre class="xml:nogutter:nocontrols" name="code"><div style="border:1px solid #f00; float:left;">div1</div>
<div style="background:#0f0; clear:right;">div2</div>
</pre><div style="border:1px solid #f00; float:left;">div1</div><div style="background:#0f0; clear:right;">div2</div><br />
這個範例則是將 div2 換成 clear:right,可以看到 div1 與 div2 仍舊重疊在一起,這是因為 div2 前一個具有 float:right 不存在,而 div1 的 float:left 不是 div2 clear:right 排除的對象。<br />
Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-40506982560465812332011-12-16T15:04:00.003+08:002023-02-25T21:41:23.871+08:00Sphinx 增量索引的方法之前有寫過一篇 <a href="http://jax-work-archive.blogspot.com/2009/05/mysql-sphinx.html">MySQL 全文檢索引擎 - Sphinx</a> 的文章,最近又把它拿出來用了,不過當時即時更新索引的問題,如今則找到解決的方法了,透過更新增量索引的方式達到即時更新。<br />
<br />
簡單的說就是利用兩個索引表的合併查詢來做到,一個是完整的索引表,一個是針對當日資料變動的增量索引。<br />
<br />
部分的設定檔如下:<br />
<pre class="cfg" name="code"># ...
source _source_base
{
# 來源-共用的設定
}
source people_full : _source_base
{
sql_query = \
SELECT \
people_profile.id, \
people_profile.main_name \
FROM people_profile
sql_query_killlist = \
SELECT id FROM people_profile \
WHERE update_date >= CURDATE()
}
# 增量索引來源,這邊只會抓出當日變動的資料
# 建議在 update_date 欄位加上 MySQL INDEX
source people_delta : people_full
{
sql_query = \
SELECT \
people_profile.id, \
people_profile.main_name \
FROM people_profile \
WHERE people_profile.update_date >= CURDATE()
}
index _index_base
{
# 索引-共用的設定
}
index people_full : _index_base
{
source = people_full
path = /var/lib/sphinxsearch/data/people_full
}
# 增量索引
index people_delta : _index_base
{
source = people_delta
path = /var/lib/sphinxsearch/data/people_delta
}
# 透過 distributed 類型來合併索引
index people
{
type = distributed
local = people_full
local = people_delta
}
# ...
</pre><br />
<br />
<strong>建立 SphinxSE 表</strong><br />
特別注意在 <strong>CONNECTION</strong> 中所指定索引表為 people。<br />
<pre class="sql" name="code">CREATE TABLE people_sphinx(
id BIGINT UNSIGNED NOT NULL COMMENT '搜尋結果的 Id',
weight INT NOT NULL COMMENT '搜尋結果的權重',
query VARCHAR(3072) NOT NULL COMMENT '搜尋的查詢條件',
INDEX(query)
)ENGINE=SPHINX
CONNECTION="sphinx://localhost:9312/people"
COMMENT='People Sphinx搜尋連接介面';
</pre><br />
<br />
<strong>SQL 的查詢測試</strong><br />
這裡使用 INNER JOIN 方式作查詢,這樣對於刪除資料的變動,就不會出現在查詢結果中。<br />
<pre class="sql" name="code">SELECT A.id, A.main_name, B.weight
FROM people_sphinx B
INNER JOIN people_profile A
USING(id)
WHERE B.query='馬丁尼茲;mode=any;limit=1000'
</pre><br />
<br />
<strong>透過 PHP 更新增量索引</strong><br />
在資料 INSERT 或 UPDATE 時,去呼叫索引更新,這樣在第一時間就可以更新索引。 <br />
<pre class="sql" name="code">shell_exec('sudo indexer --quiet --rotate people_delta 2>&1');
</pre><br />
如果 Server 是 Ubuntu,請記得在 <strong>vim /etc/sudoers</strong> 中賦予 apache 使用 indexer 的權限。<br />
<code>www-data ALL=(root) NOPASSWD: /usr/bin/indexer</code><br />
<br />
<br />
<strong>在 crontab 中加上排程</strong><br />
利用離峰時間來更新完整的索引表,由於刪除資料的變動需要更新完整索引表才有辦法移除。<br />
<code>00 00 * * * indexer --quiet --rotate --all > /dev/null 2>&1</code>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-54697940549134641752011-12-16T06:49:00.002+08:002013-06-10T22:47:27.154+08:00[PHP] 縮圖方式的比較<ul><li><a target="_blank" href="http://php.net/manual/en/function.imagecopyresized.php">imageCopyResized</a> 速度快,但縮圖的品質很粗糙</li>
<li><a target="_blank" href="http://php.net/manual/en/function.imagecopyresampled.php">imageCopyResampled</a> 縮圖的品質好,但花費的時間有點多</li>
</ul>這兩種的缺點是,都會根據圖片的像素大小,而佔用PHP的記憶體,會造成 <strong>Fatal error: Out of memory</strong> 的錯誤出現,有一種狀況是一個 3MB 大小的 JPEG 實際的像素大小卻是 128MB,再來 GD 支援的圖片類型有限,大約就是四五種常用類型。<br />
<br />
優點是指需要安裝 GD 套件,這個套件不管是在 Windows 或 Linux 上很容易找到跟安裝,在處理圖片的類型明確跟尺寸不大的情況下,使用這兩個函數是不錯的。<br />
<br />
為了改善 <strong>imageCopyResampled</strong> 效率,可以利用 <strong>imageCopyResized</strong> 做預先縮圖,例如要縮圖的大小為 100*100 時,可以先將圖片縮小成四倍或八倍,如 400*400 或 800*800,可改善超大圖造成的效率不好。<br />
<br />
<ul><li><a target="_blank" href="http://www.imagemagick.org/script/index.php">ImageMagick</a> + <a target="_blank" href="http://php.net/manual/en/book.imagick.php">Imagick</a> 使用外部的程式來處理圖片,使用指令的方式或透過 Imagick 套件來處理縮圖。</li>
</ul>缺點是透過指令的方式很不友善,且容易受到系統權限的限制,而 Imagick 套件在 Windows 上不容易找到合適的 DLL。<br />
<br />
優點是支援下面多種格式,效率快且沒有記憶體錯誤的問題。<br />
<blockquote>3FR, A, AI, ART, ARW, AVI, AVS, B, BGR, BGRA, BMP, BMP2, BMP3, BRF, BRG, C, CAL, CALS, CAPTION, CIN, CIP, CLIP, CMYK, CMYKA, CR2, CRW, CUR, CUT, DCM, DCR, DCX, DDS, DFONT, DJVU, DNG, DOT, DPX, EPDF, EPI, EPS, EPS2, EPS3, EPSF, EPSI, EPT, EPT2, EPT3, ERF, EXR, FAX, FITS, FRACTAL, FTS, G, G3, GBR, GIF, GIF87, GRADIENT, GRAY, GRB, GROUP4, HALD, HISTOGRAM, HRZ, HTM, HTML, ICB, ICO, ICON, INFO, INLINE, IPL, ISOBRL, J2C, JNG, JP2, JPC, JPEG, JPG, JPX, K, K25, KDC, LABEL, M, M2V, M4V, MAP, MAT, MATTE, MIFF, MNG, MONO, MOV, MP4, MPC, MPEG, MPG, MRW, MSL, MSVG, MTV, MVG, NEF, NULL, O, ORF, OTB, OTF, PAL, PALM, PAM, PATTERN, PBM, PCD, PCDS, PCL, PCT, PCX, PDB, PDF, PDFA, PEF, PES, PFA, PFB, PFM, PGM, PGX, PICON, PICT, PIX, PJPEG, PLASMA, PNG, PNG24, PNG32, PNG8, PNM, PPM, PREVIEW, PS, PS2, PS3, PSB, PSD, PTIF, PWP, R, RADIAL-GRADIENT, RAF, RAS, RBG, RGB, RGBA, RGBO, RLA, RLE, SCR, SCT, SFW, SGI, SHTML, SR2, SRF, STEGANO, SUN, SVG, SVGZ, TEXT, TGA, THUMBNAIL, TIFF, TIFF64, TILE, TIM, TTC, TTF, TXT, UBRL, UIL, UYVY, VDA, VICAR, VID, VIFF, VST, WBMP, WMF, WMV, WMZ, WPG, X, X3F, XBM, XC, XCF, XPM, XPS, XV, XWD, Y, YCbCr, YCbCrA, YUV</blockquote>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-85953450431010375522010-05-17T21:38:00.004+08:002013-06-11T22:05:26.038+08:00Nokia Widget 實作講堂今天去參加了第二場 Nokia 辦得活動當然要來記錄一下<br />
這一次的講師非常的 funny 分享了很多經驗<br />
而且他的公司名稱也很妙 <a target="_blank" href="http://biz.vpon.com/">有的放矢行動行銷股份有限公司</a><br />
為了找他的公司網址無意間找到 Wiki 的解釋 <a target="_blank" href="http://zh.wiktionary.org/zh-hant/%E6%9C%89%E7%9A%84%E6%94%BE%E7%9F%A2">有的放矢 - 維基詞典</a><br />
<br />
再來就是其實 Nokia 的手機大部分的硬體配備都不高<br />
並沒有想像中的好 ,有些 CPU 可能只有 20MHz<br />
可以跑這麼順主要的原因是他有一個優秀 OS (Symbian)<br />
並且可以完全的多工多執行緒(難怪我不小心操一下就沒電了)<br />
有一次開著 Google Map 再來一個全時 GPS 連線<br />
我的 Nokia 6110n 在滿電的情況下只用了半小時就沒電了 真是讓我傻眼的說<br />
在沒有備用電源的情況下我現在都不太敢再全時 GPS 連線了<br />
<br />
<br />
言歸正傳今天的 Workshow 到底再說什麼呢??<br />
Widget 基本上的架構就如我們大家所熟知的 HTML + CSS + JavaScript + Ajax<br />
但不一樣的是這裡我們可以透過 <a target="_blank" href="http://www.forum.nokia.com/Tools_Docs_and_Code/Tools/Runtimes/Web_Runtime/Nokia_Platform_Services/">Platform Services API</a> 去存取手機上的裝置<br />
從 <a target="_blank" href="http://library.forum.nokia.com/topic/Web_Developers_Library/GUID-4167BB29-78E7-4C92-82DD-34A45B88605B.html">GPS 位址</a>﹑<a target="_blank" href="http://library.forum.nokia.com/topic/Web_Developers_Library/GUID-0011B83F-274A-445B-843D-4CAA8BA977F6.html">收發簡訊</a>、<a target="_blank" href="http://library.forum.nokia.com/topic/Web_Developers_Library/GUID-1EA270E2-0954-4326-ABBA-8DC4EDE465B5.html">通訊錄</a>、<a target="_blank" href="http://library.forum.nokia.com/index.jsp?topic=/Web_Developers_Library/GUID-A359B122-CB52-492C-8C0D-0062ED0A6A89.html">行事曆 </a>等等… 一整個就很快樂<br />
感覺就像在寫 Firefox 的 plugin<br />
而且在 package 時也是用 zip 壓縮<br />
但講師說硬體資源的有限會讓你很不快樂,它只是一台手機並不是一台電腦<br />
以前在學校時寫過 PDA 所以這種切身之痛我非常瞭解<br />
盡可能別在手機上做大量運算,要不然它就 crash 給你看<br />
<br />
在實做時我又再一次的見證 Eclipse 的偉大(很好又是我熟悉的 SDK 平台)<br />
上次的 <a target="_blank" href="/2010/05/nokia-qt.html">QT</a> 也是有 Eclipse 的 <a target="_blank" href="http://www.forum.nokia.com/info/sw.nokia.com/id/b535d0b8-7ea3-4be4-880d-9941703ccd2b/Qt_for_Symbian_Developers_Library_v1_0_eclipse_plugin.zip.html" title="Qt for Symbian Developer's Library (Eclipse plug-in)">plugin </a>而這一次的 Widget 也有 <a target="_blank" href="http://www.forum.nokia.com/Tools_Docs_and_Code/Tools/Runtimes/Web_Runtime/Aptana_Plugin/" title="Nokia WRT Plug-in for Aptana Studio">plugin</a><br />
而且有很多一鍵完成的功能(雖然在 Eclipse 上本來就是這樣了)<br />
只是 Eclipse 一直都沒有很好的 Script debug 整合<br />
雖然 Aptana 上的 JavaScript 即時除錯已經很好用了,但卻僅限於 JavaScript<br />
最近拿來寫 ActionScript 也是不錯用,但還是覺得不夠好<br />
可是目前還沒找到其他的替代方案<br />
<br />
最後在跟講師換名片時幽默的說也想買一台 <a target="_blank" href="http://www.tgc-taiwan.com.tw/">Tivo</a><br />
對於<a target="_blank" href="http://www.tgc-taiwan.com.tw/"> Tivo</a> 名聲心裡小小高興一下<br />
但忠小晞我 對自己的孤陋寡聞也慚愧了一下<br />
<br />
下次來寫一個 widget 來玩玩<br />
如果老大可以給點 resource 就更好了<br />
<br />
<br />
參考資料:<br />
<a target="_blank" href="http://www.forum.nokia.com/info/sw.nokia.com/id/ec866fab-4b76-49f6-b5a5-af0631419e9c/S60_All_in_One_SDKs.html">Nokia N97 SDK</a><br />
<a target="_blank" href="http://www.forum.nokia.com/Tools_Docs_and_Code/Code_Examples/Web_Runtime.xhtml">Web Runtime Code Examples</a><br />
<a target="_blank" href="http://library.forum.nokia.com/index.jsp?topic=/Web_Developers_Library/GUID-66F1354B-5358-433C-8033-3B7B97D1028E.html">Platform Services 2.0 JavaScript API reference</a><br />
<a target="_blank" href="http://www.forum.nokia.com/info/sw.nokia.com/id/cccea743-f4e5-418f-ad9f-0a7a7f50868f/Nokia_Platform_Services_2_0.html">Nokia Platform Services 2.0 Download</a><br />
<br />
<br />
PS:<br />
既然參加免費的講習多少為對方打個廣告<br />
Nokia 目前(2010/6/11止)有舉辦創意競賽最高獎金 20 萬<br />
詳情請見:<a target="_blank" href="http://www.nokiadeveloper.com.tw/symbian/">Symbian & Maemo中文資訊站</a>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-19403417273620282402010-05-16T14:21:00.006+08:002016-02-17T15:10:25.567+08:00三分鐘瞭解 XSS 攻擊原理在看完<a target="_blank" href="http://coolshell.cn/">酷壳</a>寫的<a target="_blank" href="http://coolshell.cn/?p=2416">HTML 安全列表</a><br />
突然很想寫一篇有關 XSS 的快速教學<br />
讓更多人能瞭解何謂 XSS 安全漏洞<br />
<br />
<br />
在瞭解 XSS 之前必須知道『網站登入(Session)』的原理<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgjoS5x0edWGU3d7rC_ykmWRUwgxUHngK-25uqzjuVjI2gW4aUhF2Fyi0ecWo2z5FhiglMTsKYKsUlAR3T9_YXQKwYrw0xExjVz4noE_wIezgvbuGr04BksqFfXERxjy5as1lXy6lHDYSqV/s1600-Ic42/session%252520%2525E4%2525BD%25259C%2525E7%252594%2525A8%2525E5%25258E%25259F%2525E7%252590%252586.png"><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgjoS5x0edWGU3d7rC_ykmWRUwgxUHngK-25uqzjuVjI2gW4aUhF2Fyi0ecWo2z5FhiglMTsKYKsUlAR3T9_YXQKwYrw0xExjVz4noE_wIezgvbuGr04BksqFfXERxjy5as1lXy6lHDYSqV/s512-Ic42/session%252520%2525E4%2525BD%25259C%2525E7%252594%2525A8%2525E5%25258E%25259F%2525E7%252590%252586.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5471749440402486578" /></a><br />
簡單的說當會員成功登入後 網站會給瀏覽器一個『令牌』<br />
之後只要拿著這個『令牌』到網站上 就會被視為已經登入<br />
<br />
<br />
再來下面是 XSS 最簡單的流程<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjEcef_FpqG0YQiAame-4aezkeCHGNOticOmevvcF8ebR3g2H2X6xPUNqa7glkXUEKqy2U0nx-nTmd6I1nf5iBOH5iXUyVwiLaz6SJ_Dao-JeaOi9A_ZODFJb5jkr2Ia9Xf_VwbHNs5g-2E/s1600-Ic42/XSS%252520%2525E6%252594%2525BB%2525E6%252593%25258A%2525E6%2525B5%252581%2525E7%2525A8%25258B.png"><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjEcef_FpqG0YQiAame-4aezkeCHGNOticOmevvcF8ebR3g2H2X6xPUNqa7glkXUEKqy2U0nx-nTmd6I1nf5iBOH5iXUyVwiLaz6SJ_Dao-JeaOi9A_ZODFJb5jkr2Ia9Xf_VwbHNs5g-2E/s512-Ic42/XSS%252520%2525E6%252594%2525BB%2525E6%252593%25258A%2525E6%2525B5%252581%2525E7%2525A8%25258B.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5471749447965376162" /></a><br />
簡單的說駭客透過 JavaScript 的程式碼將你的『令牌』偷走<br />
透過這個『令牌』他也可以用你的身份順利登入網站<br />
然後偷走你的相關資料(個人資料&交易資料)<br />
然後再將這些資料賣給詐騙集團<br />
<br />
<br />
相關的參考資料:<br />
<a target="_blank" href="http://zh.wikipedia.org/zh-tw/%E8%B7%A8%E7%B6%B2%E7%AB%99%E6%8C%87%E4%BB%A4%E7%A2%BC">跨網站指令碼 - 維基百科</a><br />
<a target="_blank" href="http://www.owasp.org/index.php/Cross-site_Scripting_%28XSS%29">Cross-site Scripting (XSS) - OWASP</a><br />
<a target="_blank" href="http://twpug.net/modules/smartsection/item.php?itemid=34">XSS(Cross Site Scripting)攻擊會讓您遺失Cookie中的資料</a><br />
<a target="_blank" href="http://anti-hacker.blogspot.com/2008/01/xss.html">詳解XSS攻擊 - 網路攻防戰</a><br />
<a target="_blank" href="http://heideri.ch/jso/">HTML5 Security Cheatsheet</a>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-66615856854172876042010-05-10T21:02:00.003+08:002013-06-11T22:07:49.076+08:00Nokia Qt平台實作講堂今天去參加這個活動<br />
在講解人解說 <a target="_blank" href="http://wiki.oss.org.tw/index.php/Qt">QT</a> 的架構後<br />
想起四年前的專題會議中<br />
『<a target="_blank" href="http://yurenju.blogspot.com/">Yuren</a>』曾經分享過這個內容<br />
雖然當時聽聽就過去了<br />
沒想到今天會重新預見<br />
<br />
QT 是一種 Base 在 C++ 上的程式架構<br />
感覺像是一個 Framework<br />
特長是跨平台與嵌入式系統<br />
與 Java 不同的是它是以 compile 的方式做到跨平台<br />
雖然這樣感覺還蠻麻煩的<br />
但它的執行效率會比 Java 來得好<br />
<br />
QT 在被 Nokia 收購後內容變得更豐富了<br />
而且還有很好用的 SDK 一整個就感覺很快樂<br />
雖然還沒想到要用來做什麼<br />
但先記著吧!Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-60055559728848449242009-11-26T21:55:00.000+08:002021-10-15T19:15:43.723+08:00在 Prologic 設計的系統架構<div class="separator" style="clear: both;"><a href="https://1.bp.blogspot.com/-56peXIGDJsg/Sw6IzV7u-II/AAAAAAAANHg/dQ2ZKzNtROoBZdQKxkw0Fa5FXCGETEa4ACPcBGAYYCw/s0/system_design.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="341" data-original-width="455" src="https://1.bp.blogspot.com/-56peXIGDJsg/Sw6IzV7u-II/AAAAAAAANHg/dQ2ZKzNtROoBZdQKxkw0Fa5FXCGETEa4ACPcBGAYYCw/s0/system_design.png"/></a></div> Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-25996571566729924362009-05-02T22:52:00.013+08:002023-02-26T14:18:47.040+08:00MySQL 全文檢索引擎 - Sphinx<a href="http://www.sphinxsearch.com/" target="_blank">Sphinx 官方網站</a><br />
<ul><li><a href="#sphinx_introduce">介绍</a><br />
</li>
<li><a href="#sphinx_emphasis">重點須知</a><br />
</li>
<li><a href="#sphinx_setup">安裝 Sphinx 及 Mysql+SphinxSE</a><br />
</li>
<li><a href="#sphinx_sample">範例環境</a><br />
</li>
<li><a href="#sphinx_conf_construct">sphinx.conf 概述</a><br />
</li>
<li><a href="#sphinx_conf_deploy">sphinx.conf 配置詳解</a><br />
</li>
<li><a href="#sphinx_start">啟動搜尋引擎</a><br />
</li>
<li><a href="#sphinx_software">主要程式及運作方式</a><br />
</li>
<li><a href="#sphinx_command">命令列參數說明</a><br />
</li>
<li><a href="#sphinx_abstract">摘要使用</a><br />
</li>
<li><a href="#sphinxse_matching">搜尋的匹配模式</a><br />
</li>
<li><a href="#sphinxse_table">建立 SphinxSE 介面資料表</a><br />
</li>
<li><a href="#sphinxse_use">SphinxSE 的使用方式</a><br />
</li>
<li><a href="#sphinxse_reference">參考來源</a></li>
</ul><br />
<br />
<br />
<h4><a name="sphinx_introduce"></a>介绍</h4><br />
<blockquote>引用自:<a href="http://dev.cgfinal.com/sphinx/sphinx.html" target="_blank">Sphinx速成指南</a><br />
<p>Sphinx 是一個基於SQL的全文檢索引擎,可以結合MySQL,PostgreSQL做全文搜索,它可以提供比數據庫本身更專業的搜索功能,使得應用程序更容易實現專業化的全文檢索。 Sphinx特別為一些腳本語言設計搜索API接口,如PHP,Python,Perl,Ruby等,同時為MySQL也設計了一個存儲引擎插件。</p><br />
Sphinx的特性:<br />
<ul><li>高速索引(在新款CPU上,近10 MB/秒);<br />
</li>
<li>高速搜索(2-4G的文本量中平均查詢速度不到0.1秒);<br />
</li>
<li>高可用性(單CPU上最大可支持100 GB的文本,100M文檔);<br />
</li>
<li>提供良好的相關性排名<br />
</li>
<li>支持分佈式搜索;<br />
</li>
<li>提供文檔摘要生成;<br />
</li>
<li>提供從MySQL內部的插件式存儲引擎上搜索<br />
</li>
<li>supports boolean, phrase, and word proximity queries;<br />
</li>
<li>支持每個文檔多個全文檢索域(默認最大32個);<br />
</li>
<li>支持每個文檔多屬性;<br />
</li>
<li>支持斷詞;<br />
</li>
<li>支持單字節編碼與UTF-8編碼;<br />
</li>
<li>supports English stemming, Russian stemming, and Soundex for morphology;<br />
</li>
<li>支持MySQL(MyISAM和InnoDB表都支持);<br />
</li>
<li>支持PostgreSQL.</li>
</ul><br />
</blockquote><br />
<br />
<br />
<h4><a name="sphinx_emphasis"></a>重點須知</h4>在你決定是否採用此搜尋引擎前,先瞭解他的應用方式:<br />
<ul style="font-weight: bold; color: rgb(255, 0, 0);"><li>提供多種查詢方式與權重計算,具有 BM25 關鍵字計算,權重計算方式無法變更,但能作外部索引加權計算。<br />
</li>
<li>非網頁爬蟲的模式,是直接對 DataBase 或 XML 作數據索引。<br />
</li>
<li>沒有點擊率的計算,需要自行處理。<br />
</li>
<li>搜尋結果不包含原始資料,需要自行處理。</li>
</ul><br />
<br />
<br />
<h4><a name="sphinx_setup"></a>安裝 Sphinx 及 Mysql+SphinxSE</h4><pre class="sh" name="code"># [安裝 Sphinx 及 Mysql+SphinxSE]
#
# 下載 Sphinx 及 Mysql
wget http://lxr.mysql.com/archives/mysql-5.1/mysql-5.1.31.tar.gz
wget http://www.sphinxsearch.com/downloads/sphinx-0.9.8.1.tar.gz
# 解壓縮 Sphinx 及 Mysql
tar zxvf mysql-5.1.31.tar.gz
tar zxvf sphinx-0.9.8.1.tar.gz
# 安裝 Sphinx
cd sphinx-0.9.8.1/
./configure
make -j$(grep processor /proc/cpuinfo |wc -l)
make install
# 複製 SphinxSE Engine 至 Mysql
cd ..
cp -R ./sphinx-0.9.8.1/mysqlse ./mysql-5.1.31/storage/sphinx
# 安裝 Mysql
# 必要函式庫 automake autoconf libtool libncurses5-dev bison
cd mysql-5.1.31/
sh BUILD/autorun.sh
./configure --with-plugins=sphinx
make
make install
</pre><br />
<br />
<br />
<h4><a name="sphinx_sample"></a>範例環境</h4>這裡用一個簡單的 Blog 範例來模擬接下來的設定<br />
主要由文章和回應的架構來說明一些主要的觀念<br />
<pre class="sql" name="code">-- 資料庫: `sphinx_test`
-- 建立日期: 2009-3-30
-- 設計版本: 1.0
--
SET NAMES 'UTF8';
DROP DATABASE IF EXISTS `sphinx_test`;
CREATE DATABASE `sphinx_test` DEFAULT CHARACTER SET utf8
COLLATE utf8_unicode_ci;
USE `sphinx_test`;
-- @ blog_texts(Blog文章)
CREATE TABLE `blog_texts` (
`BlogId` INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主鍵',
`Title` CHAR(80) NOT NULL COMMENT '標題',
`Text` TEXT NOT NULL COMMENT '文章內容',
`InsertDate` DATETIME NOT NULL COMMENT '建立日期',
PRIMARY KEY(`BlogId`)
) ENGINE = INNODB COMMENT = '故事文章';
-- -----------------------------------------------------------------
-- @ blog_comments(Blog回應)
CREATE TABLE `blog_comments` (
`Id` INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主鍵',
`BlogId` INT UNSIGNED NOT NULL COMMENT '文章 ID',
`Text` TEXT NOT NULL COMMENT '回應內容',
`UpdateDate` DATETIME NOT NULL COMMENT '更新時間',
PRIMARY KEY(`Id`),
INDEX(`BlogId`),
FOREIGN KEY(`BlogId`)REFERENCES blog_texts(`BlogId`)ON DELETE CASCADE
) ENGINE = INNODB COMMENT = 'Blog 回應';
</pre><br />
<br />
<br />
<h4><a name="sphinx_conf_construct"></a>sphinx.conf 概述</h4>設定檔的主要架構由四個區塊組成:<br />
<ul><li>定義資料來源的形式<br />
</li>
<li>定義索引及分詞方式<br />
</li>
<li>配置 indexer 所使用的 memory 及 IO存取設定<br />
</li>
<li>配置 searchd 的服務端口及查詢數量等項關設定</li>
</ul><br />
<pre class="cfg" name="code">source 來源名稱1{
# 資料來源設定
}
index 索引名稱1{
source=來源名稱1
# 索引設定
}
source 來源名稱2{
# 資料來源設定
}
index 索引名稱2{
source = 來源名稱2
# 索引設定
}
indexer{
# indexer 配置選項
}
searchd{
# searchd 配置選項
}
</pre><br />
<br />
<br />
<h4><a name="sphinx_conf_deploy"></a>sphinx.conf 配置詳解</h4>預設的 conf 位址: /usr/local/etc/sphinx.conf <br />
<pre class="cfg" name="code">#
# Sphinx configuration file sample
#
# WARNING! While this sample file mentions all available options,
# it contains (very) short helper descriptions only. Please refer to
# doc/sphinx.html for details.
#
###############################################################
## data source definition
###############################################################
source s_tit
{
# 資料來源的類型。必要設定項,沒有預設值。
# 已知類型 mysql , pgsql , xmlpipe , xmlpipe2。
type = mysql
# SQL 主機連接的 IP。必要設定項,沒有預設值。
# 僅適用於SQL數據源(mysql,pgsql)。
sql_host = localhost
# SQL 主機連接的 port 選擇性設定,預設 mysql(3306),及 pgsql(5432)。
# 僅適用於SQL數據源(mysql,pgsql)。
# 這個設定必須依附在 sql_host 下。
sql_port = 3306 # optional, default is 3306
# 資料庫用戶名稱,必要設定項,沒有預設值。
sql_user = root
# 資料庫用戶密碼,必要設定項,沒有預設值。
sql_pass = 0000
# 資料庫名稱,必要設定項,沒有預設值。
sql_db = sphinx_test
# 資料庫 local 端連結介面,選擇性設定,預設為空。
#sql_sock = /tmp/mysql.sock
# MySQL 傳輸壓縮,可將索引建立時的傳輸量,預設為 0(不壓縮)。
#mysql_connect_flags = 32
# 預先查詢。選擇性,可重複,預設是空。僅適用於SQL數據源(mysql,pgsql)。
# 可用來做索引前的初始設定,如編碼設定、緩衝區大小、是否快取暫存
sql_query_pre = SET NAMES utf8
sql_query_pre = SET SESSION query_cache_type=OFF
# 主要文件擷取查詢。必要設定項,沒有預設值。僅適用於SQL數據源(mysql,pgsql)。
# 查詢指令允許使用 JOIN 及子查詢,對於資料表不能使用 AS,但資料欄位可以
sql_query = SELECT `BlogId`,`Title`,`InsertDate` FROM `blog_texts`
# 設定過濾條目,被設定的欄位將不會列入索引的範圍,之後可以作為查詢時的過濾條件,0.9.8版支援下列類型。
sql_attr_timestamp = InsertDate
#sql_attr_uint = group_id
#sql_attr_bool = is_deleted # will be packed to 1 bit
#sql_attr_str2ordinal = author_name
#sql_attr_float = long_radians
#sql_attr_multi = uint tag from ranged-query; \
# SELECT id, tag FROM tags WHERE id>=$start AND id<=$end; \
# SELECT MIN(id), MAX(id) FROM tags
# 與 sql_query_pre 的功能相同,但用於索引結束後呼叫的指令
#sql_query_post = DROP TABLE my_tmp_table
#sql_query_info =SELECT * FROM `blog_texts` WHERE `BlogId`=$id
}
#------------------------------------------------------------------------
source s_txt
{
type = mysql
sql_host = localhost
sql_user = root
sql_pass = 0000
sql_db = sphinx_test
sql_port = 3306
sql_query_pre = SET NAMES utf8
sql_query_pre = SET SESSION query_cache_type=OFF
sql_query = SELECT `BlogId`,`Text`,`InsertDate` FROM `blog_texts`
sql_attr_timestamp = InsertDate
sql_ranged_throttle = 0
sql_query_info =SELECT * FROM `blog_texts` WHERE `BlogId`=$id
}
#------------------------------------------------------------------------
source s_comment
{
type = mysql
sql_host = localhost
sql_user = root
sql_pass = 0000
sql_db = sphinx_test
sql_port = 3306
sql_query_pre = SET NAMES utf8
sql_query_pre = SET group_concat_max_len=1048576
sql_query_pre = SET SESSION query_cache_type=OFF
sql_query = \
SELECT \
`blog_comments`.`BlogId`, \
GROUP_CONCAT(`blog_comments`.`Text`)AS`CommentText`, \
COUNT(`blog_comments`.`BlogId`)AS`CommentNum`, \
`blog_texts`.`InsertDate` \
FROM \
`blog_texts` \
INNER JOIN \
`blog_comments` \
ON `blog_texts`.`BlogId`=`blog_comments`.`BlogId` \
GROUP BY `blog_comments`.`BlogId` \
sql_attr_uint = CommentNum
sql_attr_timestamp = InsertDate
sql_ranged_throttle = 0
sql_query_info =SELECT * FROM `blog_texts` WHERE `BlogId`=$id
}
###############################################################
### index definition
###############################################################
index s_tit
{
# 索引類型。預設為空(索引為簡單本地索引)。設定值有空字串或 "distributed"
#type = distributed
# 指定索引的來源,必須是上面的來源名稱
source = s_tit
# 索引記錄存放的目錄
path = /var/data/s_tit
# 文件屬性值存儲模式,預設值為 extern,可設定的屬性有 'none', 'extern' 及 'inline'.
docinfo = extern
# 鎖定記憶體緩衝區段。預設為 0(不鎖定)
mlock = 0
# 適用的形態前置處理器名單。預設為空(不應用任何前處理器)。
# 可設定的屬性有 'none', 'stem_en', 'stem_ru', 'stem_enru', 'soundex', and 'metaphone'.
# 這段在中文情況下不能使用,否則會導致無法啟動 searchd 服務。
morphology = none
# 停用詞文件清單(以空白分隔),預設為空。
# 在清單中的 word 將不會加到索引庫裡。
#stopwords = /usr/local/sphinx/data/stopwords.txt
# 詞形字典,預設為空。可以設定 word 之間的關連性。
# wordforms.txt
# walks > walk
# walked > walk
# walking > walk
#
#wordforms = /usr/local/sphinx/data/wordforms.txt
# 保留字例外文件,預設為空。
# exceptions.txt
# MS Windows => ms windows
# Microsoft Windows => ms windows
# C++ => cplusplus
# c++ => cplusplus
# C plus plus => cplusplus
#
#exceptions = /usr/local/sphinx/data/exceptions.txt
# exceptions 與 wordforms 的區別
# *exceptions 是區分大小寫的, wordforms沒有;
# *exceptions 允許檢測序列標記, wordforms 只能處理單一的 word;
# *exceptions 可以使用 charset_table 中沒有的特殊符號,wordforms 完全遵從 charset_table 中的字符;
# *exceptions 在大字典上性能會下降,wordforms 則對百萬級的條目應對自如;
# 最短的關鍵字長度
min_word_len = 1
# 編碼格式
charset_type = utf-8
# 指定 UTF-8 的編碼表
charset_table = U+FF10..U+FF19->0..9,0..9,U+FF41..U+FF5A->a..z,\
U+FF21..U+FF3A->a..z,A..Z->a..z,a..z,U+0149,U+017F,U+0138,U+00DF,\
U+00FF,U+00C0..U+00D6->U+00E0..U+00F6,U+00E0..U+00F6,\
U+00D8..U+00DE->U+00F8..U+00FE,U+00F8..U+00FE,U+0100->U+0101,\
U+0101,U+0102->U+0103,U+0103,U+0104->U+0105,U+0105,U+0106->U+0107,\
U+0107,U+0108->U+0109,U+0109,U+010A->U+010B,U+010B,U+010C->U+010D,\
U+010D,U+010E->U+010F,U+010F,U+0110->U+0111,U+0111,U+0112->U+0113,\
U+0113,U+0114->U+0115,U+0115,U+0116->U+0117,U+0117,U+0118->U+0119,\
U+0119,U+011A->U+011B,U+011B,U+011C->U+011D,U+011D,U+011E->U+011F,\
U+011F,U+0130->U+0131,U+0131,U+0132->U+0133,U+0133,U+0134->U+0135,\
U+0135,U+0136->U+0137,U+0137,U+0139->U+013A,U+013A,U+013B->U+013C,\
U+013C,U+013D->U+013E,U+013E,U+013F->U+0140,U+0140,U+0141->U+0142,\
U+0142,U+0143->U+0144,U+0144,U+0145->U+0146,U+0146,U+0147->U+0148,\
U+0148,U+014A->U+014B,U+014B,U+014C->U+014D,U+014D,U+014E->U+014F,\
U+014F,U+0150->U+0151,U+0151,U+0152->U+0153,U+0153,U+0154->U+0155,\
U+0155,U+0156->U+0157,U+0157,U+0158->U+0159,U+0159,U+015A->U+015B,\
U+015B,U+015C->U+015D,U+015D,U+015E->U+015F,U+015F,U+0160->U+0161,\
U+0161,U+0162->U+0163,U+0163,U+0164->U+0165,U+0165,U+0166->U+0167,\
U+0167,U+0168->U+0169,U+0169,U+016A->U+016B,U+016B,U+016C->U+016D,\
U+016D,U+016E->U+016F,U+016F,U+0170->U+0171,U+0171,U+0172->U+0173,\
U+0173,U+0174->U+0175,U+0175,U+0176->U+0177,U+0177,U+0178->U+00FF,\
U+00FF,U+0179->U+017A,U+017A,U+017B->U+017C,U+017C,U+017D->U+017E,\
U+017E,U+0410..U+042F->U+0430..U+044F,U+0430..U+044F,U+05D0..U+05EA,\
U+0531..U+0556->U+0561..U+0586,U+0561..U+0587,U+0621..U+063A,U+01B9,\
U+01BF,U+0640..U+064A,U+0660..U+0669,U+066E,U+066F,U+0671..U+06D3,\
U+06F0..U+06FF,U+0904..U+0939,U+0958..U+095F,U+0960..U+0963,\
U+0966..U+096F,U+097B..U+097F,U+0985..U+09B9,U+09CE,U+09DC..U+09E3,\
U+09E6..U+09EF,U+0A05..U+0A39,U+0A59..U+0A5E,U+0A66..U+0A6F,\
U+0A85..U+0AB9,U+0AE0..U+0AE3,U+0AE6..U+0AEF,U+0B05..U+0B39,\
U+0B5C..U+0B61,U+0B66..U+0B6F,U+0B71,U+0B85..U+0BB9,U+0BE6..U+0BF2,\
U+0C05..U+0C39,U+0C66..U+0C6F,U+0C85..U+0CB9,U+0CDE..U+0CE3,\
U+0CE6..U+0CEF,U+0D05..U+0D39,U+0D60,U+0D61,U+0D66..U+0D6F,\
U+0D85..U+0DC6,U+1900..U+1938,U+1946..U+194F,U+A800..U+A805,\
U+A807..U+A822,U+0386->U+03B1,U+03AC->U+03B1,U+0388->U+03B5,\
U+03AD->U+03B5,U+0389->U+03B7,U+03AE->U+03B7,U+038A->U+03B9,\
U+0390->U+03B9,U+03AA->U+03B9,U+03AF->U+03B9,U+03CA->U+03B9,\
U+038C->U+03BF,U+03CC->U+03BF,U+038E->U+03C5,U+03AB->U+03C5,\
U+03B0->U+03C5,U+03CB->U+03C5,U+03CD->U+03C5,U+038F->U+03C9,\
U+03CE->U+03C9,U+03C2->U+03C3,U+0391..U+03A1->U+03B1..U+03C1,\
U+03A3..U+03A9->U+03C3..U+03C9,U+03B1..U+03C1,U+03C3..U+03C9,\
U+0E01..U+0E2E,U+0E30..U+0E3A,U+0E40..U+0E45,U+0E47,U+0E50..U+0E59,\
U+A000..U+A48F,U+4E00..U+9FBF,U+3400..U+4DBF,U+20000..U+2A6DF,\
U+F900..U+FAFF,U+2F800..U+2FA1F,U+2E80..U+2EFF,U+2F00..U+2FDF,\
U+3100..U+312F,U+31A0..U+31BF,U+3040..U+309F,U+30A0..U+30FF,\
U+31F0..U+31FF,U+AC00..U+D7AF,U+1100..U+11FF,U+3130..U+318F,\
U+A000..U+A48F,U+A490..U+A4CF
# 指明分詞法讀取詞典文件的位置,當啟用分詞法時,為必填項。
# 在使用 LibMMSeg 作為分詞庫時,需要確保詞典文件uni.lib在指定的目錄下。
# 再使用 LibMMSeg 分詞外掛時,這個設定值才有效,不然在建立索引時會出錯。
#charset_dictpath = dict
# 忽略的字符列表,預設為空。
#ignore_chars = U+AD
# 索引的最小前綴長度,預設為0(不索引前綴)。
#min_prefix_len = 3
# 索引的最小中綴長度,預設為0(不索引中綴)。
#min_infix_len = 3
# 做前綴索引的字段列表,預設為空(所有字段均為前綴索引模式)。
#prefix_fields = url, domain
# 做中綴索引的字段列表,預設為空(所有字段均為中綴索引模式)。
#infix_fields = url, domain
# 允許前綴/中綴索引上的星號語法(或稱萬用字符)預設為 0(不使用通配符),這是為了與0.9.7版本的兼容性。設定值有 0 和 1。
enable_star = 1
#分詞,設定值有 0,1,如果要搜索中文,請指定為 1
ngram_len = 1
# 分詞字符,中文搜索必要設定。
ngram_chars = U+4E00..U+9FBF,U+3400..U+4DBF,U+20000..U+2A6DF,\
U+F900..U+FAFF,U+2F800..U+2FA1F,U+2E80..U+2EFF,U+2F00..U+2FDF,\
U+3100..U+312F,U+31A0..U+31BF,U+3040..U+309F,U+30A0..U+30FF,\
U+31F0..U+31FF,U+AC00..U+D7AF,U+1100..U+11FF,U+3130..U+318F,\
U+A000..U+A48F,U+A490..U+A4CF
# 短語邊界字符列表,預設為空。
#phrase_boundary = ., ?, !, U+2026
# 是否從輸入全文數據中去除 HTML 標記。預設為 0。設定值有 0(禁用),1(啟用)。
html_strip = 0
# 去除 HTML 標籤時要索引標籤語言的屬性列表,預設為空(不索引標記語言屬性)。
# 指定被保留並索引的 HTML 標記語言屬性,即使其他 HTML 標記被刪除。
html_index_attrs = img=alt,title; a=title;
# HTML 標籤列表,不僅這些標籤本身會被刪除,標籤之間的文字內容也會被刪除。預設為空(不刪除任何元素的內容)。
html_remove_elements = style, script
}
index dist_tit
{
# 索引類型。預設為空(索引為簡單本地索引)。設定值有空字串或 "distributed"
type = distributed
# 分佈式索引(distributed index)中的本地索引聲明,可以設定多個,預設為空。
local = s_tit
# 分佈式索引(distributed index)中的遠程代理和索引聲明,可以設定多個,預設為空。
agent = localhost:3313:remote1
agent = localhost:3314:remote2,remote3
# 遠程代理的最大連接時間,單位為毫秒,預設為 1000(1 sec)。
agent_connect_timeout = 1000
# 遠程代理的最大查詢時間,單位為毫秒,預設為 3000(3 sec)。
agent_query_timeout = 3000
# 預先開啟全部索引文件還是每次查詢時再開啟索引。預設為0(不預先開啟)。
#preopen = 1
}
#------------------------------------------------------------------------
index s_txt
{
source = s_txt
path = /var/data/s_txt
docinfo = extern
mlock = 0
morphology = none
min_word_len = 1
charset_type = utf-8
charset_table = U+FF10..U+FF19->0..9,0..9,U+FF41..U+FF5A->a..z,\
U+FF21..U+FF3A->a..z,A..Z->a..z,a..z,U+0149,U+017F,U+0138,U+00DF,\
U+00FF,U+00C0..U+00D6->U+00E0..U+00F6,U+00E0..U+00F6,\
U+00D8..U+00DE->U+00F8..U+00FE,U+00F8..U+00FE,U+0100->U+0101,\
U+0101,U+0102->U+0103,U+0103,U+0104->U+0105,U+0105,U+0106->U+0107,\
U+0107,U+0108->U+0109,U+0109,U+010A->U+010B,U+010B,U+010C->U+010D,\
U+010D,U+010E->U+010F,U+010F,U+0110->U+0111,U+0111,U+0112->U+0113,\
U+0113,U+0114->U+0115,U+0115,U+0116->U+0117,U+0117,U+0118->U+0119,\
U+0119,U+011A->U+011B,U+011B,U+011C->U+011D,U+011D,U+011E->U+011F,\
U+011F,U+0130->U+0131,U+0131,U+0132->U+0133,U+0133,U+0134->U+0135,\
U+0135,U+0136->U+0137,U+0137,U+0139->U+013A,U+013A,U+013B->U+013C,\
U+013C,U+013D->U+013E,U+013E,U+013F->U+0140,U+0140,U+0141->U+0142,\
U+0142,U+0143->U+0144,U+0144,U+0145->U+0146,U+0146,U+0147->U+0148,\
U+0148,U+014A->U+014B,U+014B,U+014C->U+014D,U+014D,U+014E->U+014F,\
U+014F,U+0150->U+0151,U+0151,U+0152->U+0153,U+0153,U+0154->U+0155,\
U+0155,U+0156->U+0157,U+0157,U+0158->U+0159,U+0159,U+015A->U+015B,\
U+015B,U+015C->U+015D,U+015D,U+015E->U+015F,U+015F,U+0160->U+0161,\
U+0161,U+0162->U+0163,U+0163,U+0164->U+0165,U+0165,U+0166->U+0167,\
U+0167,U+0168->U+0169,U+0169,U+016A->U+016B,U+016B,U+016C->U+016D,\
U+016D,U+016E->U+016F,U+016F,U+0170->U+0171,U+0171,U+0172->U+0173,\
U+0173,U+0174->U+0175,U+0175,U+0176->U+0177,U+0177,U+0178->U+00FF,\
U+00FF,U+0179->U+017A,U+017A,U+017B->U+017C,U+017C,U+017D->U+017E,\
U+017E,U+0410..U+042F->U+0430..U+044F,U+0430..U+044F,U+05D0..U+05EA,\
U+0531..U+0556->U+0561..U+0586,U+0561..U+0587,U+0621..U+063A,U+01B9,\
U+01BF,U+0640..U+064A,U+0660..U+0669,U+066E,U+066F,U+0671..U+06D3,\
U+06F0..U+06FF,U+0904..U+0939,U+0958..U+095F,U+0960..U+0963,\
U+0966..U+096F,U+097B..U+097F,U+0985..U+09B9,U+09CE,U+09DC..U+09E3,\
U+09E6..U+09EF,U+0A05..U+0A39,U+0A59..U+0A5E,U+0A66..U+0A6F,\
U+0A85..U+0AB9,U+0AE0..U+0AE3,U+0AE6..U+0AEF,U+0B05..U+0B39,\
U+0B5C..U+0B61,U+0B66..U+0B6F,U+0B71,U+0B85..U+0BB9,U+0BE6..U+0BF2,\
U+0C05..U+0C39,U+0C66..U+0C6F,U+0C85..U+0CB9,U+0CDE..U+0CE3,\
U+0CE6..U+0CEF,U+0D05..U+0D39,U+0D60,U+0D61,U+0D66..U+0D6F,\
U+0D85..U+0DC6,U+1900..U+1938,U+1946..U+194F,U+A800..U+A805,\
U+A807..U+A822,U+0386->U+03B1,U+03AC->U+03B1,U+0388->U+03B5,\
U+03AD->U+03B5,U+0389->U+03B7,U+03AE->U+03B7,U+038A->U+03B9,\
U+0390->U+03B9,U+03AA->U+03B9,U+03AF->U+03B9,U+03CA->U+03B9,\
U+038C->U+03BF,U+03CC->U+03BF,U+038E->U+03C5,U+03AB->U+03C5,\
U+03B0->U+03C5,U+03CB->U+03C5,U+03CD->U+03C5,U+038F->U+03C9,\
U+03CE->U+03C9,U+03C2->U+03C3,U+0391..U+03A1->U+03B1..U+03C1,\
U+03A3..U+03A9->U+03C3..U+03C9,U+03B1..U+03C1,U+03C3..U+03C9,\
U+0E01..U+0E2E,U+0E30..U+0E3A,U+0E40..U+0E45,U+0E47,U+0E50..U+0E59,\
U+A000..U+A48F,U+4E00..U+9FBF,U+3400..U+4DBF,U+20000..U+2A6DF,\
U+F900..U+FAFF,U+2F800..U+2FA1F,U+2E80..U+2EFF,U+2F00..U+2FDF,\
U+3100..U+312F,U+31A0..U+31BF,U+3040..U+309F,U+30A0..U+30FF,\
U+31F0..U+31FF,U+AC00..U+D7AF,U+1100..U+11FF,U+3130..U+318F,\
U+A000..U+A48F,U+A490..U+A4CF
ngram_len = 1
ngram_chars = U+4E00..U+9FBF,U+3400..U+4DBF,U+20000..U+2A6DF,\
U+F900..U+FAFF,U+2F800..U+2FA1F,U+2E80..U+2EFF,U+2F00..U+2FDF,\
U+3100..U+312F,U+31A0..U+31BF,U+3040..U+309F,U+30A0..U+30FF,\
U+31F0..U+31FF,U+AC00..U+D7AF,U+1100..U+11FF,U+3130..U+318F,\
U+A000..U+A48F,U+A490..U+A4CF
html_strip = 0
html_index_attrs = img=alt,title; a=title;
html_remove_elements = style, script
}
index dist_txt
{
type = distributed
local = s_txt
agent = localhost:3313:remote1
agent = localhost:3314:remote2,remote3
agent_connect_timeout = 1000
agent_query_timeout = 3000
}
#------------------------------------------------------------------------
index s_comment
{
source = s_comment
path = /var/data/s_comment
docinfo = extern
mlock = 0
morphology = none
min_word_len = 1
charset_type = utf-8
charset_table = U+FF10..U+FF19->0..9,0..9,U+FF41..U+FF5A->a..z,\
U+FF21..U+FF3A->a..z,A..Z->a..z,a..z,U+0149,U+017F,U+0138,U+00DF,\
U+00FF,U+00C0..U+00D6->U+00E0..U+00F6,U+00E0..U+00F6,\
U+00D8..U+00DE->U+00F8..U+00FE,U+00F8..U+00FE,U+0100->U+0101,\
U+0101,U+0102->U+0103,U+0103,U+0104->U+0105,U+0105,U+0106->U+0107,\
U+0107,U+0108->U+0109,U+0109,U+010A->U+010B,U+010B,U+010C->U+010D,\
U+010D,U+010E->U+010F,U+010F,U+0110->U+0111,U+0111,U+0112->U+0113,\
U+0113,U+0114->U+0115,U+0115,U+0116->U+0117,U+0117,U+0118->U+0119,\
U+0119,U+011A->U+011B,U+011B,U+011C->U+011D,U+011D,U+011E->U+011F,\
U+011F,U+0130->U+0131,U+0131,U+0132->U+0133,U+0133,U+0134->U+0135,\
U+0135,U+0136->U+0137,U+0137,U+0139->U+013A,U+013A,U+013B->U+013C,\
U+013C,U+013D->U+013E,U+013E,U+013F->U+0140,U+0140,U+0141->U+0142,\
U+0142,U+0143->U+0144,U+0144,U+0145->U+0146,U+0146,U+0147->U+0148,\
U+0148,U+014A->U+014B,U+014B,U+014C->U+014D,U+014D,U+014E->U+014F,\
U+014F,U+0150->U+0151,U+0151,U+0152->U+0153,U+0153,U+0154->U+0155,\
U+0155,U+0156->U+0157,U+0157,U+0158->U+0159,U+0159,U+015A->U+015B,\
U+015B,U+015C->U+015D,U+015D,U+015E->U+015F,U+015F,U+0160->U+0161,\
U+0161,U+0162->U+0163,U+0163,U+0164->U+0165,U+0165,U+0166->U+0167,\
U+0167,U+0168->U+0169,U+0169,U+016A->U+016B,U+016B,U+016C->U+016D,\
U+016D,U+016E->U+016F,U+016F,U+0170->U+0171,U+0171,U+0172->U+0173,\
U+0173,U+0174->U+0175,U+0175,U+0176->U+0177,U+0177,U+0178->U+00FF,\
U+00FF,U+0179->U+017A,U+017A,U+017B->U+017C,U+017C,U+017D->U+017E,\
U+017E,U+0410..U+042F->U+0430..U+044F,U+0430..U+044F,U+05D0..U+05EA,\
U+0531..U+0556->U+0561..U+0586,U+0561..U+0587,U+0621..U+063A,U+01B9,\
U+01BF,U+0640..U+064A,U+0660..U+0669,U+066E,U+066F,U+0671..U+06D3,\
U+06F0..U+06FF,U+0904..U+0939,U+0958..U+095F,U+0960..U+0963,\
U+0966..U+096F,U+097B..U+097F,U+0985..U+09B9,U+09CE,U+09DC..U+09E3,\
U+09E6..U+09EF,U+0A05..U+0A39,U+0A59..U+0A5E,U+0A66..U+0A6F,\
U+0A85..U+0AB9,U+0AE0..U+0AE3,U+0AE6..U+0AEF,U+0B05..U+0B39,\
U+0B5C..U+0B61,U+0B66..U+0B6F,U+0B71,U+0B85..U+0BB9,U+0BE6..U+0BF2,\
U+0C05..U+0C39,U+0C66..U+0C6F,U+0C85..U+0CB9,U+0CDE..U+0CE3,\
U+0CE6..U+0CEF,U+0D05..U+0D39,U+0D60,U+0D61,U+0D66..U+0D6F,\
U+0D85..U+0DC6,U+1900..U+1938,U+1946..U+194F,U+A800..U+A805,\
U+A807..U+A822,U+0386->U+03B1,U+03AC->U+03B1,U+0388->U+03B5,\
U+03AD->U+03B5,U+0389->U+03B7,U+03AE->U+03B7,U+038A->U+03B9,\
U+0390->U+03B9,U+03AA->U+03B9,U+03AF->U+03B9,U+03CA->U+03B9,\
U+038C->U+03BF,U+03CC->U+03BF,U+038E->U+03C5,U+03AB->U+03C5,\
U+03B0->U+03C5,U+03CB->U+03C5,U+03CD->U+03C5,U+038F->U+03C9,\
U+03CE->U+03C9,U+03C2->U+03C3,U+0391..U+03A1->U+03B1..U+03C1,\
U+03A3..U+03A9->U+03C3..U+03C9,U+03B1..U+03C1,U+03C3..U+03C9,\
U+0E01..U+0E2E,U+0E30..U+0E3A,U+0E40..U+0E45,U+0E47,U+0E50..U+0E59,\
U+A000..U+A48F,U+4E00..U+9FBF,U+3400..U+4DBF,U+20000..U+2A6DF,\
U+F900..U+FAFF,U+2F800..U+2FA1F,U+2E80..U+2EFF,U+2F00..U+2FDF,\
U+3100..U+312F,U+31A0..U+31BF,U+3040..U+309F,U+30A0..U+30FF,\
U+31F0..U+31FF,U+AC00..U+D7AF,U+1100..U+11FF,U+3130..U+318F,\
U+A000..U+A48F,U+A490..U+A4CF
ngram_len = 1
ngram_chars = U+4E00..U+9FBF,U+3400..U+4DBF,U+20000..U+2A6DF,\
U+F900..U+FAFF,U+2F800..U+2FA1F,U+2E80..U+2EFF,U+2F00..U+2FDF,\
U+3100..U+312F,U+31A0..U+31BF,U+3040..U+309F,U+30A0..U+30FF,\
U+31F0..U+31FF,U+AC00..U+D7AF,U+1100..U+11FF,U+3130..U+318F,\
U+A000..U+A48F,U+A490..U+A4CF
html_strip = 0
html_index_attrs = img=alt,title; a=title;
html_remove_elements = style, script
}
index dist_comment
{
type = distributed
local = s_comment
agent = localhost:3313:remote1
agent = localhost:3314:remote2,remote3
agent_connect_timeout = 1000
agent_query_timeout = 3000
}
###############################################################
### indexer settings
###############################################################
indexer
{
# 索引過程中記憶體的使用限制,預設為 32M。
mem_limit = 64M
# 每秒最大 I/O 操作次數,用於限制 I/O 操作。預設為0(無限制)。
#max_iops = 40
# 最大單次允許的 I/O 操作大小,以 bytes 為單位,用於I/O節流。預設為0(不限制)。
#max_iosize = 1048576
}
###############################################################
### searchd settings
###############################################################
searchd
{
# 監聽來源 IP,預設為0.0.0.0(即允許所有 IP 連結)。
#address = 127.0.0.1
# searchd 的 TCP port。預設為 3312。
port = 3312
# log 的紀錄文件位址,全部 searchd 運行時事件會被記錄在這個日誌文件中。
log = /var/log/sphinx/searchd.log
# 查詢日誌文件名,預設為空(不記錄查詢日誌)。
# 全部搜索查詢會被記錄在此文件中。
query_log = /var/log/sphinx/query.log
# 最大的查詢請求時間,單位是秒。預設是5秒。
# searchd 將強制關閉在此時間內未能成功發出查詢的客戶端連接。
read_timeout = 5
# 並行執行的搜索的數目。預設為0(無限制)。
max_children = 30
# searchd 進程 ID 文件名。必選項。
pid_file = /var/log/sphinx/searchd.pid
# 守護進程在記憶體中為每個索引所保持並返回給客戶端的匹配數目的最大值。預設為1000
max_matches = 1000
# 防止 searchd 輪換在需要預取大量數據的索引時停止響應。預設為1(啟用無縫(seamless)輪換)
seamless_rotate = 1
# 是否在啟動時強制重新打開所有索引文件。預設為0(不重新打開)。
preopen_indexes = 0
# 索引輪換成功之後,是否刪除以.old為擴展名的索引拷貝。預設為1(刪除這些索引拷貝)。
unlink_old = 1
}
# --eof--
</pre><br />
<br />
<br />
<h4><a name="sphinx_start"></a>啟動搜尋引擎</h4>開始建立索引及啟動搜尋引擎<br />
<pre class="sh" name="code"># 為所有 sphinx.conf 中設定的資料來源建立索引
/usr/local/bin/indexer --config /usr/local/etc/sphinx.conf --all
# 以 sphinx.conf 中的設定啟動搜尋引擎
/usr/local/bin/searchd --config /usr/local/etc/sphinx.conf
</pre><br />
<br />
動態更新索引的方法<br />
<pre class="sh" name="code"># 更新所有資料來源
/usr/local/bin/indexer --rotate --config /usr/local/etc/sphinx.conf --all
# 更新特定的資料來源(s_tit),並且不顯示任何訊息
/usr/local/bin/indexer --quiet --rotate --config /usr/local/etc/sphinx.conf s_tit
</pre><br />
<br />
<br />
<h4><a name="sphinx_software"></a>主要程式及運作方式</h4>Sphinx 主要有以下部分:<br />
<ul><li><strong>indexer</strong>: 建立索引庫的程序,在查詢前必須先建立索引庫。<br />
</li>
<li><strong>search</strong>: 提供 console 下的搜尋介面,可用於測試用。<br />
</li>
<li><strong>searchd</strong>: 主要的 service 程序,以 port 為連接介面去對索引庫取得資料。<br />
</li>
<li><strong>SphinxAPI</strong>: 連結 searchd 的客戶端 API,目前支援的 script 語言有(PHP, Python, Perl, Ruby)。<br />
</li>
<li><strong>SphinxSE</strong>: 透過 MySQL 連結 searchd 的資料表引擎。</li>
</ul><br />
<a href="http://3.bp.blogspot.com/_b8lN_UbLoEc/SfxX6fhPoOI/AAAAAAAAEP8/6vcfnSO72tQ/Sphinx%E6%9E%B6%E6%A7%8B%E5%9C%96.jpg"><img src="http://3.bp.blogspot.com/_b8lN_UbLoEc/SfxX6fhPoOI/AAAAAAAAEP8/6vcfnSO72tQ/s512/Sphinx%E6%9E%B6%E6%A7%8B%E5%9C%96.jpg" alt="Sphinx 架構圖"id="BLOGGER_PHOTO_ID_5331232721304789218" /></a><br />
<br />
<br />
<h4><a name="sphinx_command"></a>命令列參數說明</h4><strong>indexer</strong><br />
格式:indexer [OPTIONS] [indexname1 [indexname2 [...]]]<br />
<ul><li><strong>--config <file></strong> (<strong>-c <file></strong>精簡指令) 指定 sphinx.conf 的位址,預設為 /usr/local/etc/sphinx.conf<br />
</li>
<li><strong>--all</strong> 為所有資料來源建立索引,在利用 cron 定期更新索引庫時,可使用此參數更新所有資料來源的索引<br />
</li>
<li><strong>--rotate</strong> 動態更新來源索引,可用在 searchd 仍處於啟動的狀態。<br />
</li>
<li><strong>--quiet</strong> 告訴 indexer 不輸出任何訊息,除非有一個錯誤。在 cron 上使用時非常方便。<br />
</li>
<li><strong>--noprogress</strong> 不顯示進展的細節訊息。而在最後輸出的細節訊息(如文件索引,索引的速度等等)<br />
</li>
<li><strong>--merge <dst-index> <src-index></strong> 合併 <src-index> 到 <dst-index>,<dst-index>會保存有合併後的結果,<src-index> 不會被修改。<br />
</li>
<li><strong>--merge-dst-range <attr> <min> <max></strong>合併時過濾 <dst-index> 僅保留 <attr> 值在 <min> 和 <max> (包含)的記錄.</li>
</ul><br />
<br />
<strong>searchd</strong><br />
格式:searchd [OPTIONS]<br />
<ul><li><strong>--help</strong> (<strong>-h</strong> 精簡指令) 列出所有的參數說明。<br />
</li>
<li><strong>--config <file></strong> (<strong>-c <file></strong> 精簡指令) 指定 sphinx.conf 的位址,預設為 /usr/local/etc/sphinx.conf<br />
</li>
<li><strong>--stop</strong> 停止 searchd 引擎的運作<br />
</li>
<li><strong>--console</strong> 強制使用 console 介面(windows)<br />
</li>
<li><strong>--port portnumber</strong> (<strong>-p</strong> 精簡指令) 強制變更 searchd 連結端口(port)的位址<br />
</li>
<li><strong>--index <index></strong> (<strong>-i</strong> 精簡指令) 指定唯一的搜尋索引,主要用於測試用。</li>
</ul><br />
<br />
<strong>search</strong><br />
格式:search [OPTIONS] word1 [word2 [word3 [...]]]<br />
<br />
一般選項:<br />
<ul><li><strong>--config <file></strong>(<strong>-c <file></strong> 精簡指令) 讓 search 使用特定的 conf 檔作為配置,就像前面 indexer。<br />
</li>
<li><strong>--index <index></strong>(<strong>-i <index></strong> 精簡指令) 讓 search 限制搜尋的索引檔,預設會嘗試搜尋所有 conf 檔中列出的索引。<br />
</li>
<li><strong>--stdin</strong> tells <strong class="filename">search</strong> 讓 search 接受查詢從標準輸入,而不是命令行。這可用在測試目的上,即可以透過管線輸入或 script。</li>
</ul><br />
<br />
設置匹配選項:<br />
<ul><li><strong>--any</strong> (<strong>-a</strong> 精簡指令) 匹配所有的搜尋關鍵字。<br />
</li>
<li><strong>--phrase</strong> (<strong>-p</strong> 精簡指令) 短語匹配。<br />
</li>
<li><strong>--boolean</strong> (<strong>-b</strong> 精簡指令) 布林表達式匹配。<br />
</li>
<li><strong>--ext</strong> (<strong>-e</strong> 精簡指令) 查詢匹配一個Sphinx內部查詢語言表達式。<br />
</li>
<li><strong>--ext2</strong> (<strong>-e2</strong> 精簡指令) 查詢匹配一個Sphinx內部查詢語言表達式。<br />
</li>
<li><strong>--filter <attr> <v></strong> (<strong>-f <attr> <v></strong> 精簡指令) 過慮條件,可用在過慮 conf 中以 attr 標註的欄位,(--filter InsertDate 2009)過慮出建立日期為 2009 的資料</li>
</ul><br />
<br />
處理結果輸出選項:<br />
<ul><li><strong>--limit <count></strong>(<strong>-l count</strong> 精簡指令) 輸出 row 的數量(預設為20)。<br />
</li>
<li><strong>--offset <count></strong>(<strong>-o <count></strong>精簡指令) 輸出 row 的起始(預設為0)。<br />
</li>
<li><strong>--group <attr></strong>(<strong>-g <attr></strong>精簡指令) specifies that results should be grouped together based on the attribute specified. Like the GROUP BY clause in SQL, it will combine all results where the attribute given matches, and returns a set of results where each returned result is the best from each group. Unless otherwise specified, this will be the best match on relevance.<br />
</li>
<li><strong>--groupsort <expr></strong>(<strong>-gs <expr></strong>精簡指令) instructs that when results are grouped with <code>-group</code>, the expression given in <expr>shall determine the order of the groups. Note, this does not specify which is the best item within the group, only the order in which the groups themselves shall be returned.<br />
</li>
<li><strong>--sortby <clause></strong>(<strong>-s <clause></strong>精簡指令) specifies that results should be sorted in the order listed in <clause>. This allows you to specify the order you wish results to be presented in, ordering by different columns. For example, you could say <code>--sortby "@weight DESC entrytime DESC"</code> to sort entries first by weight (or relevance) and where two or more entries have the same weight, to then sort by the time with the highest time (newest) first. You will usually need to put the items in quotes (<code>--sortby "@weight DESC"</code>) or use commas (<code>--sortby @weight,DESC</code>) to avoid the items being treated separately. Additionally, like the regular sorting modes, if <code>--group</code> (grouping) is being used, this will state how to establish the best match within each group.<br />
</li>
<li><strong>--sortexpr expr</strong> (<strong>-S expr</strong> 精簡指令) specifies that the search results should be presented in an order determined by an arithmetic expression, stated in expr. For example: <code>--sortexpr "@weight + ( user_karma + ln(pageviews) )*0.1"</code> (again noting that this will have to be quoted to avoid the shell dealing with the asterisk). Extended sort mode is discussed in more detail under the <code>SPH_SORT_EXTENDED</code> entry under the Sorting modes chapter of the manual.<br />
</li>
<li><strong>--sort=date</strong> 指定以降幕排序的欄位,必須在 conf 中以 attr 標註的欄位。<br />
</li>
<li><strong>--rsort=date</strong> 指定以升幕排序的欄位,必須在 conf 中以 attr 標註的欄位。</li>
</ul><br />
<br />
<br />
<h4><a name="sphinx_abstract"></a>摘要使用</h4>Sphinx 的 API 提供摘要產生的功能<br />
在下載的安裝包中可以找到這份範例<br />
<pre class="php" name="code"><?php
//
// $Id: test2.php 910 2007-11-16 11:43:46Z shodan $
//
require ( "sphinxapi.php" );
$docs = array
(
"this is my test text to be highlighted, and for the sake ".
"of the testing we need to pump its length somewhat",
"another test text to be highlighted, below limit",
"test number three, without phrase match",
"final test, not only without phrase match, but also above ".
"limit and with swapped phrase text test as well",
);
$words = "test text";
$index = "test1";
$opts = array
(
"before_match" => "<b>",
"after_match" => "</b>",
"chunk_separator" => " ... ",
"limit" => 60,
"around" => 3,
);
foreach ( array(0,1) as $exact )
{
$opts["exact_phrase"] = $exact;
print "exact_phrase=$exact\n";
$cl = new SphinxClient ();
$res = $cl->BuildExcerpts ( $docs, $index, $words, $opts );
if ( !$res )
{
die ( "ERROR: " . $cl->GetLastError() . ".\n" );
} else
{
$n = 0;
foreach ( $res as $entry )
{
$n++;
print "n=$n, res=$entry\n";
}
print "\n";
}
}
//
// $Id: test2.php 910 2007-11-16 11:43:46Z shodan $
//
?>
</pre><br />
<br />
<br />
<h4><a name="sphinxse_matching"></a>搜尋的匹配模式</h4><table border="1" style="font-size:11px; width=98%;"><tr><th width="80">匹配模式</th><th>SPH_MATCH_ALL<br />
匹配所有</th><th>SPH_MATCH_ANY<br />
匹配任意</th><th>SPH_MATCH_PHRASE<br />
短语匹配</th><th>SPH_MATCH_EXTENDED<br />
内部查询</th></tr>
<tr><th>觀察</th><td>必須包含前後順序,所有 word 都要包含。</td><td>word 符合前後順序權重高,中文會拆字查詢。</td><td>必須包前後順序,而且是有序比對(排除標點符號)。</td><td>無特定前後順序,但有符合前後順序的權重比較高。</td></tr>
<tr><th>權重特性</th><td>Max match_word lemgth</td><td>(match_word1 lemgth)^2+<br />
(match_word2 lemgth)^2</td><td>match_word lemgth</td><td>BM25</td></tr>
<tr><th>最高權重值</th><td>{順序完全批配的word數}</td><td>{所有 word 數}^2</td><td>{word數}<br />
沒有較小的權重,此為唯一權重值。</td><td>?</td></tr>
<tr><th>word 對結果的影響</th><td>用所有的 word 進行查詢,只要有一個 word 不符合就不撈出來。</td><td>符合其中一個word。</td><td>完全符合前後順序與所有word。</td><td>符合全數word。</td></tr>
<tr><th>查詢時 word 的先後</th><td>可以不同。</td><td>正確的順序權重較高。</td><td>必要。順序不同不會出現在結果裡。</td><td>前後順序影響權重。</td></tr>
<tr><th>適合用途</th><td>適合不會打錯的查詢,word 很少的查詢。</td><td>多 word 的查詢。</td><td>完全精準查詢。</td><td>適合不會打錯的查詢,word 很少的查詢。</td></tr>
<tr><th>特性</th><td>word 越多,查詢越少。有一個 word 沒有就找不到了。</td><td>word 越多,資料越多。</td><td>幾乎等於 SQL like。</td><td>word 越多,資料越少,權重值會拉開,且權重值是根據所有可能結果之間的差異做權重。</td></tr>
</table><br />
<br />
<br />
<h4><a name="sphinxse_table"></a>建立 SphinxSE 表</h4>SphinxSE 的使用方式是利用一個虛擬的資料表去與 searchd 作連結<br />
這個表本身不會儲存任何資料,也不能新增資料<br />
利用這個資料表可以作任何 SQL SELECT 的操作(JOIN ...)<br />
<pre class="sql" name="code">-- @ sphinx_interface(Sphinx搜尋連接介面)
CREATE TABLE `sphinx_interface` (
-- 前三個為必要欄位,
-- 欄位屬性順序必須為 INTEGER,INTEGER,VARCHAR
-- 分別標記為(id),匹配權重(weight),查詢(query)
-- 不限定欄位名稱
-- 同時 id 及 query 必須建立索引。
`id` INT NOT NULL COMMENT '搜尋結果的 Id',
`weight` INT NOT NULL COMMENT '搜尋結果的權重',
`query` VARCHAR(3072) NOT NULL COMMENT '搜尋的查詢條件',
-- 額外欄位,需與 sphinx.conf 中 sql_attr 設定的欄位一致,
-- 欄位屬性必須為 INTEGER,VARCHAR 或 TIMESTAMP,
-- 此處的設定可作後續的排序或過濾用。
`insertdate` VARCHAR(3072) NOT NULL COMMENT '日期',
`commentnum` INTEGER COMMENT '回應總數',
INDEX(id),
INDEX(query)
)ENGINE=SPHINX
-- CONNECTION 的格式為 sphinx://HOST:PORT/INDEXNAME
-- 建議先不加 INDEXNAME,等在查詢時在決定 index
CONNECTION="sphinx://localhost:3312/"
COMMENT='Sphinx搜尋連接介面';
</pre><br />
<br />
<br />
<h4><a name="sphinxse_use"></a>SphinxSE 的使用方式</h4><ul><li><strong>query</strong> 查詢文本<br />
<br />
</li>
<li><strong>mode</strong> 匹配模式.必須是 "all", "any", "phrase", "boolean" 或 "extended",預設為“all”<br />
<br />
</li>
<li><strong>sort</strong> 匹配項排序模式必須是“relevance”, “attr_desc”, “attr_asc”, “time_segments”或“extended”之一。除了“relevance”模式,其他模式中還必須在一個冒號後附上屬性名(或“extended”模式中的排序子句)。<br />
<pre class="sql:nocontrols" name="code">... WHERE query='test;sort=attr_asc:group_id';
... WHERE query='test;sort=extended:@weight desc, group_id asc';</pre><br />
</li>
<li><strong>offset</strong> 結果集中的偏移量,預設是0。<br />
<br />
</li>
<li><strong>limit</strong> 從結果集中獲取的匹配項數目,預設為20。<br />
<br />
</li>
<li><strong>index</strong> 待搜索的索引:<br />
<pre class="sql:nocontrols" name="code">... WHERE query='test;index=test1;';
... WHERE query='test;index=test1,test2,test3;';</pre><br />
</li>
<li><strong>minid</strong> , <strong>maxid</strong> 匹配文檔ID的最小值和最大值<br />
<br />
</li>
<li><strong>weights</strong> 逗號分隔的列表,指定Sphinx全文數據字段的權值<br />
<pre class="sql:nocontrols" name="code">... WHERE query='test;weights=1,2,3;';</pre><br />
</li>
<li><strong>filter</strong> , <strong>!filter</strong> 逗號分隔的列表,指定一個屬性名和一系列可匹配的屬性值:<br />
<pre class="sql:nocontrols" name="code">-- 僅包括群組 1, 5 和 19
... WHERE query='test;filter=group_id,1,5,19;';
-- 排除的群組 3 和 11
... WHERE query='test;!filter=group_id,3,11;';</pre><br />
</li>
<li><strong>range</strong> , <strong>!range</strong> 逗號分隔的列表,指定一個屬性名和該屬性可匹配的最小值和最大值:<br />
<pre class="sql:nocontrols" name="code">-- 僅包括群組 3 至 7 之間 的 group_id
... WHERE query='test;range=group_id,3,7;';
-- 排除的群組 5 至 25 之間的 group_id
... WHERE query='test;!range=group_id,5,25;';</pre><br />
</li>
<li><strong>maxmatches</strong> 此查詢最大匹配的數量:<br />
<pre class="sql:nocontrols" name="code">... WHERE query='test;maxmatches=2000;';</pre><br />
</li>
<li><strong>groupby</strong> 分組(group-by)函數和屬性:<br />
<pre class="sql:nocontrols" name="code">... WHERE query='test;groupby=day:published_ts;';
... WHERE query='test;groupby=attr:group_id;';</pre><br />
</li>
<li><strong>groupsort</strong> 分組(group-by)排序子句<br />
<pre class="sql:nocontrols" name="code">... WHERE query='test;groupsort=@count desc;';</pre><br />
</li>
<li><strong>indexweights</strong> 逗號分隔的列表,指定一系列索引名和搜索時這些索引對應的權值<br />
<pre class="sql:nocontrols" name="code">... WHERE query='test;indexweights=idx_exact,2,idx_stemmed,1;';</pre></li>
</ul><br />
<h4><a name="sphinxse_reference"></a>參考來源</h4><a href="http://www.sphinxsearch.com/docs/manual-0.9.8.html" target="_blank">Sphinx 0.9.8.1 reference manual</a> <br />
<a href="http://www.coreseek.cn/uploads/pdf/sphinx_doc_zhcn_0.9.pdf" target="_blank">Coreseek 全文檢索服務器2.0 (Sphinx 0.9.8) 參考手冊</a> <br />
<a href="http://dev.cgfinal.com/sphinx/sphinx.html" target="_blank">Sphinx速成指南</a><br />
<br />
<a href="http://blog.jctalk.info/2008/11/sphinx.html" target="_blank">Sphinx 自由開放原始碼全文搜尋引擎</a> <br />
<a href="http://www.xxlinux.com/linux/article/development/database/20090108/14811_3.html" target="_blank">ubuntu下Mysql+sphinx+中文分词安装配置</a> <br />
<a href="http://www.chineselinuxuniversity.net/articles/18720.shtml" target="_blank">Mysql+sphinx+中文分词简介(ubuntu)</a> <br />
<a href="http://www.ibm.com/developerworks/cn/opensource/os-php-sphinxsearch/index.html" target="_blank">用 PHP 构建自定义搜索引擎</a>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com7tag:blogger.com,1999:blog-5946530704742130970.post-43173903209459338072009-04-17T20:10:00.000+08:002009-04-17T20:29:51.507+08:00利用 switch 該善程式碼的閱讀性一個簡單的網頁 Script 架構,不外乎一個進入點及一個輸出點,由上到下一個很單純的流程。<br /><br />在資料驗證上也是一個接一個,但卻數量多的時候,卻會讓整體流程看起來很複雜,很不直覺。<br /><br />一個基本 switch 的撰寫方法,用在代替有結構的 IF 時還蠻放便的。<br /><pre class="php" name="code"><br /><?php<br />switch ($_GET['type']) {<br /> // 當 $_GET['type']=="apple" 時進入程式片段 <br /> case "apple": <br /> //程式片段...<br /> break;<br /> <br /> // 當 $_GET['type']=="bar" 時進入程式片段 <br /> case "bar":<br /> //程式片段...<br /> break;<br /> <br /> // 當以上都不符合,預設進入程式片段 <br /> default :<br /> //程式片段...<br /> break;<br />}<br />?><br /></pre><br /><br />這個例子,將判斷式移到 case 上,讓原本的 switch 更加彈性,但這不是主要目的,在 switch 裡主要的好處是可以利用 break 跳出流程,這樣的用法跟 function 很像,利用跳出的方式結束剩下的程序,在區塊中檢查到錯誤的數入參數時,直接丟出訊息並讓輸出程序去處理顯示的問題。<br /><pre class="php" name="code"><br /><?php<br />switch (true){<br /> case ($_GET['type']=='view'):<br /> //其他判斷或動作...<br /> break;<br /> <br /> case ($_GET['action']=='delete'):<br /> //其他判斷或動作...<br /> break;<br /> <br /> case ($_GET['action']=='add'):<br /> //標題檢查<br /> $title=trim($_GET['title']);/*去除頭尾空白*/<br /> $title=htmlspecialchars($title,ENT_QUOTES);/*HTML 跳脫*/<br /> $title=mb_substr($title, 0, 100);/*100字剪裁*/<br /> if(!$title){ //利用 if 判斷,錯誤則跳出 switch<br /> // 列出錯誤訊息<br /> break;<br /> }<br /> <br /> //訊息檢查<br /> $msg=trim($_GET['msg']);/*去除頭尾空白*/<br /> $msg=htmlspecialchars($msg,ENT_QUOTES);/*HTML 跳脫*/<br /> $msg=mb_substr($msg, 0, 1000);/*1000字剪裁*/<br /> if(!$msg)){ //利用 if 判斷,錯誤則跳出 switch<br /> // 列出錯誤訊息<br /> break;<br /> }<br /><br /> //其他判斷或動作...<br /> break;<br /> <br /> default :<br /> // 列出錯誤訊息<br /> break;<br />} <br />?><br /></pre><br /><br />再看看如果用 IF 去處理輸入驗證時,必須將所有資料先做處理,後面再一個一個去判斷,在閱讀上很不直覺,如果中間有大量的輸入參數要處理時,會讓人找很久。<br /><pre class="php" name="code"><br /><?php<br />if($_GET['type']=='view'){<br /> //其他判斷或動作...<br /> <br />}elseif($_GET['action']=='delete'){<br /> //其他判斷或動作...<br /> <br />}elseif($_GET['action']=='add'){<br /> //標題檢查<br /> $title=trim($_GET['title']);/*去除頭尾空白*/<br /> $title=htmlspecialchars($title,ENT_QUOTES);/*HTML 跳脫*/<br /> $title = mb_substr($title, 0, 100);/*100字剪裁*/<br /> <br /> //訊息檢查<br /> $msg=trim($_GET['msg']);/*去除頭尾空白*/<br /> $msg=htmlspecialchars($msg,ENT_QUOTES);/*HTML 跳脫*/<br /> $msg = mb_substr($msg, 0, 1000);/*1000字剪裁*/<br /> <br /> if(!$title){<br /> // 列出錯誤訊息<br /> }elseif(!$msg){<br /> // 列出錯誤訊息<br /> }else{<br /> //其他判斷或動作...<br /> }<br /><br /> break;<br />}else{<br /> // 列出錯誤訊息<br />}<br />?><br /></pre><br /><br />參考來源:<br /><a href="http://tw.php.net/switch">PHP: switch - Manual</a>Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0tag:blogger.com,1999:blog-5946530704742130970.post-31719896766583489642009-04-16T15:08:00.003+08:002010-05-27T00:28:06.732+08:00[Web] Cookie 小觀念的補述今天看到<a href="http://www.jaceju.net/blog/">網站製作學習誌</a>中提到 COOKIE 的問題<br /><blockquote>引用來源:<a href="http://www.jaceju.net/blog/?p=477">[Web] Cookie 小觀念</a><br /><br />setcookie('test', 'abc');<br />var_dump($_COOKIE);<br /><br />如果你回答的是空陣列的話,那就表示你瞭解 Cookie 的作用了。<br /></blockquote><br /><br />除了這個問題之外<br />其實在 COOKIE 簡單的操作上<br />有一些問題也常常被忽視<br /><br />在過去的文章中也有提到我遇到的問題<br /><a href="/2008/01/cookie.html">cookie 路徑問題</a><br /><a href="/2008/12/apache-301-redirect.html">apache 301 Redirect 永久重新導向</a><br /><br />例如:<br /><pre class="sh" name="code"><br />http://domain.com/<br />http://www.domain.com/<br />http://www.domain.com/dir<br /></pre><br /><br />這三種都是 COOKIE 有可能的儲存路徑<br />這常常讓 COOKIE 的存取上造成問題<br />上層路徑的程式無法取得下層路徑的 COOKIE<br />而下層路徑在 COOKIE 寫入上卻會不小心造成雙份的問題Jax Huhttp://www.blogger.com/profile/01953021685585893658noreply@blogger.com0