段時(shí)間因工作需求,特地學(xué)習(xí)了下sp_send_dbmail的使用,好不容易完工來和大家分享一下,不談理論,只管實(shí)踐!
如下是實(shí)際需求:
-- =============================================
-- Title: 集團(tuán)資質(zhì)一覽表
-- Description1:<1、距離到期日期1年內(nèi)和已過期的發(fā)到期提醒>
-- Description2:<2、表頭【非附件】:公司名稱、發(fā)證部門、證書名稱、類別、等級、到期日期、預(yù)警級別>
-- Description3:<3、預(yù)警級別:假設(shè)距離到期日期月數(shù)為N。一級:N<=3;二級:3<N<=6;三級:N>=6>
-- Description4:<4、提醒人員:郵件提醒
-- =============================================
在這里sp_send_dbmail的參數(shù)不去做詳述(我也不懂~),實(shí)際過程中我們需要用到的并不多,只需下面幾行就能發(fā)送html格式的郵件了
Exec dbo.sp_send_dbmail
@profile_name='crm***', --發(fā)件人姓名
@recipients='156240***@qq.com', --郵箱(多個(gè)用;隔開)
@body=@tableHTML, --消息主體
@body_format='HTML', --指定消息的格式,一般文本直接去掉即可,發(fā)送html格式的內(nèi)容需加上
@subject ='資質(zhì)到期預(yù)警'; -- 消息的主題
下面最主要的部分就是@tableHTML了,在這里我們使用兩種方式去拼接html。
1.通過sql CAST 函數(shù),網(wǎng)上的示例大多數(shù)是這種,愚笨的我不太看得懂,只能依瓢。
declare @tableHTML varchar(max)
SET @tableHTML =
N'<H1 style="text-align:center">資質(zhì)相關(guān)信息</H1>' +
N'<table border="1" cellpadding="3" cellspacing="0" align="center">' +
N'<tr><th width=100px" >公司名稱</th>'+
N'<th width=250px>發(fā)證部門</th><th width=150px>證書名稱</th>'+
N'<th width=50px>類別</th><th width=50px>等級</th>'+
N'<th width=60px>到期日期</th><th width=60px>預(yù)警級別</th></tr>'+
CAST ( (
select td = p.CompanyName, '',td = p.DeptName, '',td=p.Name,'', td = p.QualificationType, '',td = p.Level, '',td = p.ExpireDates, '',td=p.YJ,''
from(
select
CompanyName,DeptName,Name,QualificationType,Level,Convert(varchar(50),ExpireDate,111)ExpireDates,
case when DATEDIFF(mm,getDate(),ExpireDate)<=3 then '一級預(yù)警'
when DATEDIFF(mm,getDate(),ExpireDate)<=6 then '二級預(yù)警' else '三級預(yù)警'end YJ
from T_Market***_JTZZ
where 12>=DATEDIFF(mm,getDate(),ExpireDate)
) p order by p.ExpireDates asc
FOR XML PATH('tr'), TYPE
) AS NVARCHAR(MAX) ) +
N'</table>' ;
Exec dbo.sp_send_dbmail
@profile_name='crm***',
@recipients = '156240***@qq.com',
@subject='資質(zhì)到期預(yù)警',
@body=@tableHTML,
@body_format = 'HTML' ;
2.通過游標(biāo)動(dòng)態(tài)繪制html,感覺這種更方便,雖然寫起來有點(diǎn)啰嗦,但很靈活。
BEGIN
declare @tableHTML varchar(max)
declare @Companyname varchar(250) --公司名稱
declare @Deptname varchar(250) --發(fā)證部門
declare @Certname varchar(250) --證書名稱
declare @Certtype varchar(50) --證書類別
declare @Certlevel varchar(50) --證書等級
declare @Expirdate varchar(20) --到期時(shí)間
declare @Warnlevel varchar(20) --預(yù)警級別
begin
set @tableHTML = '<html><body><table><tr><td><p><font color="#000080" size="3" face="Verdana">您好!</font></p><p style="margin-left:30px;"><font size="3" face="Verdana">以下資質(zhì)即將到期或已過期,請盡快辦理資質(zhì)延續(xù):</font></p></td></tr>';
--創(chuàng)建臨時(shí)表#tbl_result
create table #tbl_result(companyname varchar(250),deptname varchar(250),certname varchar(250),certtype varchar(50),certlevel varchar(50),expirdate varchar(20),warnlevel varchar(10));
insert into #tbl_result
select CompanyName,DeptName,Name,QualificationType,Level,convert(varchar(20),ExpireDate,23) ExpireDate,case when ms<=3 then '一級' when ms>3 and ms<=6 then '二級' else '三級' end warnlevel
from (
select *,Datediff(MONTH,GETDATE(),ExpireDate) ms
from T_Market***_JTZZ
where ExpireDate is not null and Datediff(MONTH,GETDATE(),ExpireDate)<=12
) res;
declare @counts int;
select @counts=count(*) from #tbl_result;
--- 提醒列表
if(@counts>0)
begin
set @tableHTML=@tableHTML+'<tr><td><table border="1" style="border:1px solid #d5d5d5;border-collapse:collapse;border-spacing:0;margin-left:30px;margin-top:20px;"><tr style="height:25px;background-color: rgb(219, 240, 251);"><th style="width:100px;">公司名稱</th><th style="width:200px;">發(fā)證部門</th><th>證書名稱</th><th style="width:60px;">類別</th><th style="width:80px;">等級</th><th style="width:100px;">到期日期</th><th style="width:80px;">預(yù)警級別</th></tr>';
--申明游標(biāo)
Declare cur_cert Cursor for
select companyname,deptname,certname,certtype,certlevel,expirdate,warnlevel from #tbl_result order by expirdate;
--打開游標(biāo)
open cur_cert
--循環(huán)并提取記錄
Fetch Next From cur_cert Into @Companyname,@Deptname,@Certname,@Certtype,@Certlevel,@Expirdate,@Warnlevel
While (@@Fetch_Status=0)
begin
set @tableHTML = @tableHTML + '<tr><td align="center">'+@Companyname+'</td>';
set @tableHTML = @tableHTML + '<td align="center">'+@Deptname+'</td>';
set @tableHTML = @tableHTML + '<td align="center">'+@Certname+'</td>';
set @tableHTML = @tableHTML + '<td align="center">'+@Certtype+'</td>';
set @tableHTML = @tableHTML + '<td align="center">'+@Certlevel+'</td>';
set @tableHTML = @tableHTML + '<td align="center">'+@Expirdate+'</td>';
set @tableHTML = @tableHTML + '<td align="center">'+@Warnlevel+'</td></tr>';
--繼續(xù)遍歷下一條記錄
Fetch Next From cur_cert Into @Companyname,@Deptname,@Certname,@Certtype,@Certlevel,@Expirdate,@Warnlevel
end
--關(guān)閉游標(biāo)
Close cur_cert
--釋放游標(biāo)
Deallocate cur_cert
set @tableHTML = @tableHTML + '</table></td></tr>';
end
-- 發(fā)送郵件
exec msdb.dbo.sp_send_dbmail
@profile_name='crm***',
@recipients='156240***@qq.com',
@body=@tableHTML,
@body_format='HTML',
@subject ='資質(zhì)到期預(yù)警';
-- 刪除臨時(shí)表(#tbl_result)
if object_id('tempdb..#tbl_result') is not null
begin
drop table #tbl_result;
end
end
END
看起來無疑第二種特別啰嗦,但個(gè)人感覺很好理解,游標(biāo)拼接html部分思路很清楚,以上兩個(gè)方式均經(jīng)過實(shí)踐,如需使用只需要將其中對應(yīng)的字段、數(shù)據(jù)源替換掉即可,感謝諸位賞足,有什么不足之處還望大家見諒,
做項(xiàng)目的過程中,我們經(jīng)常遇到需要把信息存儲在本地的情況,比如權(quán)限驗(yàn)證的token、用戶信息、埋點(diǎn)計(jì)數(shù)、客戶配置的皮膚信息或語言種類等,我們可以暫存一下避免瀏覽器不必要的請求和客戶多余操作,給客戶使用帶來方便。
上一篇講了JavaScript瀏覽器端數(shù)據(jù)存儲方案之Cookie篇,這篇文章主要介紹localStorage和sessionStorage。
HTML5中 Web Storage 的出現(xiàn),主要是為了彌補(bǔ)使用 Cookie 作為本地存儲的不足。Cookie 存儲的數(shù)據(jù)量非常小,而且數(shù)據(jù)會(huì)自動(dòng)攜帶到請求頭里,但服務(wù)器端可能并不關(guān)心這些數(shù)據(jù),所以會(huì)造成帶寬的浪費(fèi)。
Web Storage 提供了兩個(gè)存儲對象:localStorage 和 sessionStorage。
sessionStorage 存儲的數(shù)據(jù)僅在本次會(huì)話有用,會(huì)話結(jié)束后會(huì)自動(dòng)失效,而且數(shù)據(jù)僅在當(dāng)前窗口有效,同一源下新窗口也訪問不到其他窗口基于sessionStorage 存儲的數(shù)據(jù)。也是由于這些特性,導(dǎo)致 sessionStorage 的使用場景會(huì)比較少。
localStorage 可以永久存儲,而且同源下數(shù)據(jù)多窗口也能共享。看起來很美好,但 localStorage 也有短板,絕大多數(shù)瀏覽器有 5M 的大小限制。但是這不足以成為大家使用 localStorage 的障礙,要知道 Cookie 只有 4K 的大小,多了一千多倍
localStorage 的基本使用
localStorage.setItem("b","isaac");//設(shè)置b為"isaac" var b = localStorage.getItem("b");//獲取b的值,為"isaac" var a = localStorage.key(0); // 獲取第0個(gè)數(shù)據(jù)項(xiàng)的鍵名,此處即為“b” localStorage.removeItem("b");//清除c的值 localStorage.clear();//清除當(dāng)前域名下的所有l(wèi)ocalstorage數(shù)據(jù)
這里和大家分享一段我們在VUE項(xiàng)目中封裝使用的localStorage,大家可以借鑒實(shí)現(xiàn)的思路
有兩點(diǎn)需要注意一下。在 setItem 時(shí),可能會(huì)達(dá)到大小限制,最好加上錯(cuò)誤捕捉
另外在存儲容量快滿時(shí),會(huì)造成 getItem 性能急劇下降。我們下面看看 localStorage 有哪些腦洞大開的用法
緩存靜態(tài)文件
你不禁要問,HTTP 協(xié)議不是本來就支持緩存文件嗎,為什么還要使用 localStorage 來緩存?為了可編程化,通俗一點(diǎn)說就是把命運(yùn)握在自己手中。
HTTP 協(xié)議的緩存,可以由用戶瀏覽器清除或禁用緩存,也可以由 Web 服務(wù)器設(shè)置過期時(shí)間或不緩存。對于前端工程師,這更像是一個(gè)黑盒,想要決定文件是訪問緩存還是訪問遠(yuǎn)程顯得有些力不從心了。
使用 localStorage 控制文件緩存的方式有兩種:
這兩種方式一般都會(huì)提前做好緩存過期策略,通常是使用版本號來控制,下面還會(huì)細(xì)講。否則文件新版上線,用戶客戶端還是舊版,這就麻煩大了,而且這類問題,還不好調(diào)試不好重現(xiàn)。
使用 Loader 加載靜態(tài)文件
由于請求都是動(dòng)態(tài)發(fā)出的,所以可以對請求攔截處理。大致流程如下:
這個(gè)方式有個(gè)開源庫:basket.js(可以在github上訪問)
借助服務(wù)器端將靜態(tài)文件 inline 化
這個(gè)方式比上面那種更進(jìn)一步,在第一次響應(yīng)時(shí)把需要放入 localStorage 的文件都內(nèi)聯(lián)進(jìn) html 中,后面每次響應(yīng)只要文件版本沒有變化,都是渲染一段從 localStorage 加載該文件的代碼。這樣做的好處是可以有效減少請求次數(shù),即使是第一次。
版本號不匹配(版本號可記在 Cookie 中,第一次訪問沒有版本號),服務(wù)端響應(yīng)內(nèi)容:
<script> function script2ls(id) { var script = document.getElementById(id); if (script) { localStorage[id] = script.innerHTML; } } </script> <script id="jquery.js">...jquery source code...</script> <script>script2ls('jquery.js')</script>
版本號匹配,服務(wù)端響應(yīng)內(nèi)容:
<script> function ls2script(id) { var script = document.createElement('script'); script.text = localStorage[id]; document.head.appendChild(script); } </script> <script>ls2script('jquery.js')</script>
不過使用 localStorage 緩存文件會(huì)有 XSS 的風(fēng)險(xiǎn),而且造成的傷害可能是永久的
同源窗口通信
你可能不禁又要問,不是有 postMessage 嗎?沒錯(cuò) postMessage 確實(shí)可以用于窗口或 iframe 間通信,但是前提是你必須拿到打開新窗或 iframe 的句柄對象:
var popup = window.open(...popup details...); popup.postMessage("hello there!", "http://example.com");
這樣在新窗中再打開新窗,似乎就不好傳遞消息了。
你可能還想問,為什么要在窗口間通信?好問題,沒有應(yīng)用場景的技術(shù)都是耍流氓。像多窗口共用的一些組件,而且對數(shù)據(jù)實(shí)時(shí)同步都有較高要求的都會(huì)是這個(gè)技術(shù)的應(yīng)用場景。比如通知中心上面的未讀數(shù)量,兩個(gè)窗口,A 窗口更新為 8,切到 B 窗口還是 9,這就造成了體驗(yàn)不一致,這個(gè)例子可能還覺得無關(guān)痛癢;再比如購物車,兩個(gè)產(chǎn)品窗口,A 窗口添加到購物車,切到 B 窗口添加到購物車,發(fā)現(xiàn)沒有 A 添加的產(chǎn)品,這樣就比較嚴(yán)重了。這當(dāng)然也可以通過每個(gè)窗口都與后臺建立連接來更新,但用戶如果開十幾個(gè)窗口就開銷大了。
有了同源窗口通信,我們就可以只有一個(gè)窗口與后臺建立連接,收到更新后,廣播給其他窗口就可以。說了這么多,實(shí)現(xiàn)原理是怎樣的呢?
其實(shí)原理也簡單,每次 localStorage 中有任何變動(dòng)都會(huì)觸發(fā)一個(gè) storage 事件,所有窗口都監(jiān)聽這個(gè)事件,一旦有窗口更新 localStorage,其他窗口都會(huì)收到通知,根據(jù)事件中的 key 把不關(guān)心的變動(dòng)過濾掉。原理是很簡單,但是要實(shí)現(xiàn)一套完整的廣播機(jī)制還是有些復(fù)雜,你需要:
不用擔(dān)心,已經(jīng)有了不錯(cuò)的開源實(shí)現(xiàn):diy/intercom.js、tejacques/crosstab
作為前端 DB 的存儲介質(zhì)
你可能不滿足于用鍵值對保存數(shù)據(jù),你還想保存更復(fù)雜的數(shù)據(jù)結(jié)構(gòu)。
靈活存取 json 格式的數(shù)據(jù):typicode/lowdb
通過 sql 對數(shù)據(jù) CURD 操作:agershun/alasql
表單自動(dòng)持久化
在填寫表單時(shí),遇到瀏覽器奔潰或者誤操作導(dǎo)致填寫內(nèi)容丟失,此刻用戶的內(nèi)心也應(yīng)該是奔潰的。誤操作還可以加一個(gè) beforeunload 事件,在關(guān)閉瀏覽器或跳出當(dāng)前頁前提醒一下用戶。那瀏覽器崩潰呢,將數(shù)據(jù)變更實(shí)時(shí)保存到后臺,這樣似乎開銷很大,實(shí)時(shí)保存到 localStorage 是個(gè)不錯(cuò)的解決方案,真巧,也有一個(gè)開源實(shí)現(xiàn):simsalabim/sisyphus
TML5是最新的HTML標(biāo)準(zhǔn),是專門為承載豐富的web內(nèi)容而設(shè)計(jì)的,并且無需額外插件。接下來小編就為大家講解HTML5的5種存儲方式:
1.本地存儲localstorage
存儲方式:
以鍵值對(Key-Value)的方式存儲,永久存儲,永不失效,除非手動(dòng)刪除。
大小:
每個(gè)域名5M
支持情況:
注意:IE9 localStorage不支持本地文件,需要將項(xiàng)目署到服務(wù)器,才可以支持!
if(window.localStorage){ alert('This browser supports localStorage'); }else{ alert('This browser does NOT support localStorage'); }
常用的API:
getItem //取記錄
setIten//設(shè)置記錄
removeItem//移除記錄
key//取key所對應(yīng)的值
clear//清除記錄
存儲的內(nèi)容:
數(shù)組,圖片,json,樣式,腳本。。。(只要是能序列化成字符串的內(nèi)容都可以存儲)
2.本地存儲sessionstorage
HTML5 的本地存儲 API 中的 localStorage 與 sessionStorage 在使用方法上是相同的,區(qū)別在于 sessionStorage 在關(guān)閉頁面后即被清空,而 localStorage 則會(huì)一直保存。
3.離線緩存(application cache)
本地緩存應(yīng)用所需的文件
使用方法:
①配置manifest文件
頁面上:
...
Manifest 文件:
manifest 文件是簡單的文本文件,它告知瀏覽器被緩存的內(nèi)容(以及不緩存的內(nèi)容)。
manifest 文件可分為三個(gè)部分:
①CACHE MANIFEST - 在此標(biāo)題下列出的文件將在首次下載后進(jìn)行緩存
②NETWORK - 在此標(biāo)題下列出的文件需要與服務(wù)器的連接,且不會(huì)被緩存
③FALLBACK - 在此標(biāo)題下列出的文件規(guī)定當(dāng)頁面無法訪問時(shí)的回退頁面(比如 404 頁面)
完整demo:
CACHE MANIFEST # 2016-07-24 v1.0.0 /theme.css /main.js NETWORK: login.jsp FALLBACK: /html/ /offline.html
服務(wù)器上:manifest文件需要配置正確的MIME-type,即 "text/cache-manifest"。
如Tomcat:
manifest text/cache-manifest
常用API:
核心是applicationCache對象,有個(gè)status屬性,表示應(yīng)用緩存的當(dāng)前狀態(tài):
0(UNCACHED) : 無緩存, 即沒有與頁面相關(guān)的應(yīng)用緩存
1(IDLE) : 閑置,即應(yīng)用緩存未得到更新
2 (CHECKING) : 檢查中,即正在下載描述文件并檢查更新
3 (DOWNLOADING) : 下載中,即應(yīng)用緩存正在下載描述文件中指定的資源
4 (UPDATEREADY) : 更新完成,所有資源都已下載完畢
5 (IDLE) : 廢棄,即應(yīng)用緩存的描述文件已經(jīng)不存在了,因此頁面無法再訪問應(yīng)用緩存
相關(guān)的事件:
表示應(yīng)用緩存狀態(tài)的改變:
checking : 在瀏覽器為應(yīng)用緩存查找更新時(shí)觸發(fā)
error : 在檢查更新或下載資源期間發(fā)送錯(cuò)誤時(shí)觸發(fā)
noupdate : 在檢查描述文件發(fā)現(xiàn)文件無變化時(shí)觸發(fā)
downloading : 在開始下載應(yīng)用緩存資源時(shí)觸發(fā)
progress:在文件下載應(yīng)用緩存的過程中持續(xù)不斷地下載地觸發(fā)
updateready : 在頁面新的應(yīng)用緩存下載完畢觸發(fā)
cached : 在應(yīng)用緩存完整可用時(shí)觸發(fā)
Application Cache的三個(gè)優(yōu)勢:
① 離線瀏覽
② 提升頁面載入速度
③ 降低服務(wù)器壓力
注意事項(xiàng):
1. 瀏覽器對緩存數(shù)據(jù)的容量限制可能不太一樣(某些瀏覽器設(shè)置的限制是每個(gè)站點(diǎn) 5MB)
2. 如果manifest文件,或者內(nèi)部列舉的某一個(gè)文件不能正常下載,整個(gè)更新過程將視為失敗,瀏覽器繼續(xù)全部使用老的緩存
3. 引用manifest的html必須與manifest文件同源,在同一個(gè)域下
4. 瀏覽器會(huì)自動(dòng)緩存引用manifest文件的HTML文件,這就導(dǎo)致如果改了HTML內(nèi)容,也需要更新版本才能做到更新。
5. manifest文件中CACHE則與NETWORK,F(xiàn)ALLBACK的位置順序沒有關(guān)系,如果是隱式聲明需要在最前面
6. FALLBACK中的資源必須和manifest文件同源
7. 更新完版本后,必須刷新一次才會(huì)啟動(dòng)新版本(會(huì)出現(xiàn)重刷一次頁面的情況),需要添加監(jiān)聽版本事件。
8. 站點(diǎn)中的其他頁面即使沒有設(shè)置manifest屬性,請求的資源如果在緩存中也從緩存中訪問
9. 當(dāng)manifest文件發(fā)生改變時(shí),資源請求本身也會(huì)觸發(fā)更新
離線緩存與傳統(tǒng)瀏覽器緩存區(qū)別:
1. 離線緩存是針對整個(gè)應(yīng)用,瀏覽器緩存是單個(gè)文件
2. 離線緩存斷網(wǎng)了還是可以打開頁面,瀏覽器緩存不行
3. 離線緩存可以主動(dòng)通知瀏覽器更新資源
4.Web SQL
關(guān)系數(shù)據(jù)庫,通過SQL語句訪問
Web SQL 數(shù)據(jù)庫 API 并不是 HTML5 規(guī)范的一部分,但是它是一個(gè)獨(dú)立的規(guī)范,引入了一組使用 SQL 操作客戶端數(shù)據(jù)庫的 APIs。
支持情況:
Web SQL 數(shù)據(jù)庫可以在最新版的 Safari, Chrome 和 Opera 瀏覽器中工作。
核心方法:
①openDatabase:這個(gè)方法使用現(xiàn)有的數(shù)據(jù)庫或者新建的數(shù)據(jù)庫創(chuàng)建一個(gè)數(shù)據(jù)庫對象。
②transaction:這個(gè)方法讓我們能夠控制一個(gè)事務(wù),以及基于這種情況執(zhí)行提交或者回滾。
③executeSql:這個(gè)方法用于執(zhí)行實(shí)際的 SQL 查詢。
打開數(shù)據(jù)庫:
var db = openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024,fn); //openDatabase() 方法對應(yīng)的五個(gè)參數(shù)分別為:數(shù)據(jù)庫名稱、版本號、描述文本、數(shù)據(jù)庫大小、創(chuàng)建回調(diào)
執(zhí)行查詢操作:
var db = openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024); db.transaction(function (tx) { tx.executeSql('CREATE TABLE IF NOT EXISTS WIN (id unique, name)'); });
插入數(shù)據(jù):
var db = openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024); db.transaction(function (tx) { tx.executeSql('CREATE TABLE IF NOT EXISTS WIN (id unique, name)'); tx.executeSql('INSERT INTO WIN (id, name) VALUES (1, "winty")'); tx.executeSql('INSERT INTO WIN (id, name) VALUES (2, "LuckyWinty")'); });
讀取數(shù)據(jù):
db.transaction(function (tx) { tx.executeSql('SELECT * FROM WIN', [], function (tx, results) { var len = results.rows.length, i; msg = "
查詢記錄條數(shù): " + len + "
"; document.querySelector('#status').innerHTML += msg; for (i = 0; i <>
alert(results.rows.item(i).name ); } }, null); });
由這些操作可以看出,基本上都是用SQL語句進(jìn)行數(shù)據(jù)庫的相關(guān)操作,如果你會(huì)MySQL的話,這個(gè)應(yīng)該比較容易用。
5.IndexedDB
索引數(shù)據(jù)庫 (IndexedDB) API(作為 HTML5 的一部分)對創(chuàng)建具有豐富本地存儲數(shù)據(jù)的數(shù)據(jù)密集型的離線 HTML5 Web 應(yīng)用程序很有用。同時(shí)它還有助于本地緩存數(shù)據(jù),使傳統(tǒng)在線 Web 應(yīng)用程序(比如移動(dòng) Web 應(yīng)用程序)能夠更快地運(yùn)行和響應(yīng)。
*請認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。