很多网友建议在YbRapidSolution for MVC框架的基础上实现CMS功能,以方便进行内容的管理,加快前端页面的开发速度。因此花了一段时间,实现了一套CMS内容发布系统并已集成至YbRapidSolution for MVC框架中。

本CMS当前实现了CMS参数设置、栏目管理、文章管理、文档管理、评论管理、问卷调查等功能。首先看看本CMS使用的主要技术及其整体架构图:

上图中的架构可以说和YbRapidSolution for MVC的架构基本是一致的,如下将对主要的技术要点进行总结和介绍:

1、CMS参数设置

CMS参数设置主要对一些全局信息的内容和设置进行管理,如网站标题、Logo、版权信息等。其底层使用了应用程序设置公共组件。仅需简单实现自Yb.Data.Provider.BaseSettings类即可,同时可随意声明自身需要的属性,非常方便,代码如下:

 using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
namespace YbRapidSolution.Services.Cms
{
[Serializable]
[DataContract]
public class CmsSetting : Yb.Data.Provider.BaseSettings
{
/// <summary>
/// 是否多站点共享
/// </summary>
public override bool AsShared
{
get { return false; }
} #region 站点参数 /// <summary>
/// 站点名称
/// </summary>
[DisplayName("站点名称")]
[Description("本站点的名称")]
[DefaultValue("Yellbuy")]
[DataMember]
public string SiteName { get; set; }
/// <summary>
/// 站点域名
/// </summary>
[DisplayName("站点域名")]
[Description("本站点的域名")]
[DefaultValue("http://Yellbuy.com")]
[DataMember]
public string SiteDomain { get; set; }
/// <summary>
/// 站点标题
/// </summary>
[DisplayName("站点标题")]
[Description("本站点的标题")]
[DefaultValue("Yellbuy")]
[DataMember]
public string SiteTitle { get; set; }
/// <summary>
/// 站点Logo地址
/// </summary>
[DisplayName("站点Logo地址")]
[Description("本站点的Logo地址")]
[DefaultValue("")]
[DataMember]
public string SiteLogoUrl { get; set; }
/// <summary>
/// 站点Banner地址
/// </summary>
[DisplayName("站点Banner地址")]
[Description("本站点的Banner地址")]
[DefaultValue("")]
[DataMember]
public string SiteBannerUrl { get; set; }
/// <summary>
/// 站点版权信息
/// </summary>
[DisplayName("站点版权信息")]
[Description("本站点的版权信息")]
[DefaultValue("© 2010-2015 YELLBY.版权所有")]
[DataMember]
public string SiteCopyRight { get; set; }
/// <summary>
/// 站点关键字
/// </summary>
[DisplayName("站点关键字")]
[Description("本站点的关键字")]
[DefaultValue("")]
[DataMember]
public string SiteKeyword { get; set; }
/// <summary>
/// 站点描述
/// </summary>
[DisplayName("站点描述")]
[Description("本站点的描述信息")]
[DefaultValue("")]
[DataMember]
public string SiteDescription { get; set; }
/// <summary>
/// 站点首页路径
/// </summary>
[DisplayName("站点首页路径")]
[Description("本站点的首页路径")]
[DefaultValue("")]
[DataMember]
public string SiteHomePath { get; set; } #endregion #region 邮件参数 /// <summary>
/// Email地址
/// </summary>
[DisplayName("Email地址")]
[Description("Email地址")]
[DefaultValue("19892257@qq.com")]
[DataMember]
public virtual string EmailAddress { get; set; } /// <summary>
/// Email显示名
/// </summary>
[DisplayName("Email显示名")]
[Description("Email显示名")]
[DefaultValue("Yellbuy")]
[DataMember]
public string EmailDisplayName { get; set; } /// <summary>
/// Email主机名
/// </summary>
[DisplayName("Email主机名")]
[Description("Email主机名")]
[DefaultValue("")]
[DataMember]
public string EmailHost { get; set; } /// <summary>
/// Email端口号
/// </summary>
[DisplayName("Email端口号")]
[Description("Email端口号")]
[DefaultValue()]
[DataMember]
public int EmailPort { get; set; } /// <summary>
/// Email用户名
/// </summary>
[DisplayName("Email用户名")]
[Description("Email用户名")]
[DefaultValue("")]
[DataMember]
public string EmailUsername { get; set; } /// <summary>
/// Email密码
/// </summary>
[DisplayName("Email密码")]
[Description("Email密码")]
[DefaultValue("")]
[DataMember]
public string EmailPassword { get; set; } /// <summary>
/// Email是否使用SSL
/// </summary>
[DisplayName("Email是否使用SSL")]
[Description("Email是否使用SSL")]
[DefaultValue(false)]
[DataMember]
public bool EnableSsl { get; set; } /// <summary>
/// Email是否使用默认证书
/// </summary>
[DisplayName("Email是否使用默认证书")]
[Description("Email是否使用默认证书")]
[DefaultValue(false)]
[DataMember]
public bool EmailUseDefaultCredentials { get; set; } /// <summary>
/// Gets a friendly email account name
/// </summary>
public string GetFriendlyName()
{
if (!String.IsNullOrWhiteSpace(this.EmailDisplayName))
return this.EmailAddress + " (" + this.EmailDisplayName + ")";
return this.EmailAddress;
} #endregion #region 页面参数 /// <summary>
/// 页面缓存类型
/// </summary>
[DisplayName("页面缓存类型")]
[Description("0:不缓存,1:绝对时间失效,2:相对时间失效")]
[DefaultValue()]
[DataMember]
public int PageCacheType { get; set; }
/// <summary>
/// 页面缓存时间
/// </summary>
[DisplayName("页面缓存时间")]
[Description("单位:秒(S)")]
[DefaultValue()]
[DataMember]
public int PageCacheInterval { get; set; }
/// <summary>
/// 内容过滤字符串
/// </summary>
[DisplayName("内容过滤字符串")]
[Description("多个内容使用英文半角“,”符合进行分割")]
[DefaultValue("")]
[DataMember]
public string PageFilter { get; set; }
/// <summary>
/// 允许上传的图片格式
/// </summary>
[DisplayName("允许上传的图片格式")]
[Description("多个内容使用英文半角“,”符合进行分割")]
[DefaultValue(".gif,.jpg,.jpeg,.bmp,.png")]
[DataMember]
public string PageUploadAllowImageFormat { get; set; } public string[] GetUploadAllowImageFormat()
{
if (string.IsNullOrWhiteSpace(PageUploadAllowImageFormat)) return new string[];
return PageUploadAllowImageFormat.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries);
}
/// <summary>
/// 允许上传的图片大小
/// </summary>
[DisplayName("允许上传的图片大小")]
[Description("单位:KB,0表示不限制大小")]
[DefaultValue()]
[DataMember]
public int PageUploadAllowImageSize { get; set; }
/// <summary>
/// 允许上传的音频格式
/// </summary>
[DisplayName("允许上传的音频格式")]
[Description("多个内容使用英文半角“,”符合进行分割")]
[DefaultValue(".mid,.mp3,.wma")]
[DataMember]
public string PageUploadAllowAudioFormat { get; set; }
public string[] GetUploadAllowAudioFormat()
{
if (string.IsNullOrWhiteSpace(PageUploadAllowAudioFormat)) return new string[];
return PageUploadAllowAudioFormat.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
}
/// <summary>
/// 允许上传的音频大小
/// </summary>
[DisplayName("允许上传的音频大小")]
[Description("单位:KB,0表示不限制大小")]
[DefaultValue()]
[DataMember]
public int PageUploadAllowAudioSize { get; set; }
/// <summary>
/// 允许上传的视频格式
/// </summary>
[DisplayName("允许上传的视频格式")]
[Description("多个内容使用英文半角“,”符合进行分割")]
[DefaultValue(".wmv,.asf,.avi,.mpg,.ram,.rm,.swf")]
[DataMember]
public string PageUploadAllowVideoFormat { get; set; }
public string[] GetUploadAllowVideoFormat()
{
if (string.IsNullOrWhiteSpace(PageUploadAllowVideoFormat)) return new string[];
return PageUploadAllowVideoFormat.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
}
/// <summary>
/// 允许上传的视频大小
/// </summary>
[DisplayName("允许上传的视频大小")]
[Description("单位:KB,0表示不限制大小")]
[DefaultValue()]
[DataMember]
public int PageUploadAllowVideoSize { get; set; }
/// <summary>
/// 允许上传的文档文件格式
/// </summary>
[DisplayName("允许上传的文档文件格式")]
[Description("多个内容使用英文半角“,”符合进行分割")]
[DefaultValue(".rar,.zip,doc,docx,xls,xlsx,pdf")]
[DataMember]
public string PageUploadAllowDocumentFormat { get; set; }
public string[] GetUploadAllowDocumentFormat()
{
if (string.IsNullOrWhiteSpace(PageUploadAllowDocumentFormat)) return new string[];
return PageUploadAllowDocumentFormat.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
}
/// <summary>
/// 允许上传的文档文件大小
/// </summary>
[DisplayName("允许上传的文档文件大小")]
[Description("单位:KB,0表示不限制大小")]
[DefaultValue()]
[DataMember]
public int PageUploadAllowDocumentSize { get; set; }
/// <summary>
/// 允许上传的其他文件格式
/// </summary>
[DisplayName("允许上传的其他文件格式")]
[Description("多个内容使用英文半角“,”符合进行分割")]
[DefaultValue(".html,.htm,.css,cshtml,aspx")]
[DataMember]
public string PageUploadAllowFileFormat { get; set; }
public string[] GetUploadAllowFileFormat()
{
if (string.IsNullOrWhiteSpace(PageUploadAllowFileFormat)) return new string[];
return PageUploadAllowFileFormat.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
}
/// <summary>
/// 允许上传的其他文件大小
/// </summary>
[DisplayName("允许上传的文件大小")]
[Description("单位:KB,0表示不限制大小")]
[DefaultValue()]
[DataMember]
public int PageUploadAllowFileSize { get; set; }
#endregion #region 用户设置 /// <summary>
/// 后台登录使用验证码
/// </summary>
[DisplayName("后台登录使用验证码")]
[Description("后台登录使用验证码")]
[DefaultValue(false)]
[DataMember]
public bool AdminUseAuthCode { get; set; } /// <summary>
/// 允许用户注册
/// </summary>
[DisplayName("允许用户注册")]
[Description("是否允许用户注册")]
[DefaultValue(false)]
[DataMember]
public bool UserAllowRegister { get; set; }
/// <summary>
/// 用户注册是否需要使用Email验证
/// </summary>
[DisplayName("用户注册是否需要使用Email验证")]
[Description("用户注册是否需要使用Email验证")]
[DefaultValue(false)]
[DataMember]
public bool UserUseEmailValidate { get; set; }
/// <summary>
/// 用户注册时是否使用验证码
/// </summary>
[DisplayName("用户注册时是否使用验证码")]
[Description("用户注册时是否使用验证码")]
[DefaultValue(false)]
[DataMember]
public bool UserRegisterUseAuthCode { get; set; }
/// <summary>
/// 用户登录时是否使用验证码
/// </summary>
[DisplayName("用户登录时是否使用验证码")]
[Description("用户登录时是否使用验证码")]
[DefaultValue(false)]
[DataMember]
public bool UserLoginUseAuthCode { get; set; }
/// <summary>
/// 用户回复方式
/// </summary>
[DisplayName("用户回复方式")]
[Description("0:不允许回复,1:允许匿名用户回复,2:仅允许登录用户回复")]
[DefaultValue()]
[DataMember]
public int UserReplyKind { get; set; }
/// <summary>
/// 用户回复时是否输入验证码
/// </summary>
[DisplayName("用户回复时是否输入验证码")]
[Description("用户回复时是否输入验证码")]
[DefaultValue(false)]
[DataMember]
public bool UserReplyUseAuthCode { get; set; }
/// <summary>
/// 用户回复时是否加载编辑器
/// </summary>
[DisplayName("用户回复时是否加载编辑器")]
[Description("用户回复时是否加载编辑器")]
[DefaultValue(false)]
[DataMember]
public bool UserReplyUseEditor { get; set; }
/// <summary>
/// 用户回复内容是否需要审核才允许发布
/// </summary>
[DisplayName("用户回复是否需要审核")]
[Description("用户回复内容是否需要审核才允许发布")]
[DefaultValue(false)]
[DataMember]
public bool UserReplyAudit { get; set; } #endregion #region 水印/缩放设置 /// <summary>
/// 水印类型
/// </summary>
[DisplayName("水印类型")]
[Description("水印类型,0:文字水印,1:图片水印")]
[DefaultValue()]
[DataMember]
public int WatermarkKind { get; set; }
/// <summary>
/// 文字水印内容
/// </summary>
[DisplayName("文字水印内容")]
[Description("文字水印内容")]
[DefaultValue("YELLBUY")]
[DataMember]
public string WatermarkText { get; set; }
/// <summary>
/// 文字水印字体大小
/// </summary>
[DisplayName("文字水印字体大小")]
[Description("单位:pt")]
[DefaultValue()]
[DataMember]
public int WatermarkFontSize { get; set; }
/// <summary>
/// 文字水印字体名称
/// </summary>
[DisplayName("文字水印字体名称")]
[Description("")]
[DefaultValue("Arial")]
[DataMember]
public string WatermarkFontFamily { get; set; }
/// <summary>
/// 文字水印字体颜色
/// </summary>
[DisplayName("文字水印字体颜色")]
[Description("")]
[DefaultValue("#Blue")]
[DataMember]
public string WatermarkFontColor { get; set; }
/// <summary>
/// 图片水印的图片地址
/// </summary>
[DisplayName("图片水印的图片地址")]
[Description("")]
[DefaultValue("")]
[DataMember]
public string WatermarkImgUrl { get; set; }
/// <summary>
/// 图片水印的高度
/// </summary>
[DisplayName("图片水印的高度")]
[Description("单位:px")]
[DefaultValue()]
[DataMember]
public int WatermarkImgHeight { get; set; }
/// <summary>
/// 图片水印的宽度
/// </summary>
[DisplayName("图片水印的宽度")]
[Description("单位:px")]
[DefaultValue()]
[DataMember]
public int WatermarkImgWidth { get; set; }
/// <summary>
/// 图片水印的透明度
/// </summary>
[DisplayName("图片水印的透明度")]
[Description("在0%至100%之间,100%表示不透明,0%表示完全透明")]
[DefaultValue(0.5f)]
[DataMember]
public float WatermarkImgOpacity { get; set; }
/// <summary>
/// 图片水印的位置
/// </summary>
[DisplayName("图片水印的位置")]
[Description("0:左上角,1:右上角,2:左下角,3:右下角")]
[DefaultValue()]
[DataMember]
public int WatermarkImgRegion { get; set; } #endregion
}
}

CMS参数设置

数据访问方面,仅需调用SettingApi.LoadSettings即可进行上述参数设置信息的加载,使用SettingApi.SaveSettings方法则进行参数设置信息的保存,最终的参数设置信息将保存至数据库中。数据访问的具体代码如下(注:如下代码中对CMS参数设置信息进行了内存缓存处理以提高系统性能):

     public CmsSetting Load()
{
string key = CACHE_PATTERN_KEY;
return _cacheManager.Get(key, () =>
{
var pv = SettingApi.LoadSettings<CmsSetting>();
return pv;
});
} public void Save(CmsSetting setting)
{
if (setting == null)
throw new ArgumentNullException("setting"); SettingApi.SaveSettings(setting);
//移除缓存
_cacheManager.RemoveByPattern(CACHE_PATTERN_KEY);
}

CMS参数设置的数据访问方法

2、Entity Framework中的多主键的配置

文章、文档、评论等的管理,通常需要支持草稿和发布两种状态,而且两种状态在管理时互不影响。例如发布了一篇文章后,还应可继续编辑该文章并保持其为草稿状态,在未重新发布之前,不会覆盖已发布的内容;而一旦草稿的内容发布后将自动更新发布的内容,相反也可提供草稿的撤销功能(即用已发布的内容来覆盖现有草稿的内容)。因此对文章、文档和评论的管理使用了双主键,一个主键代表标识(字段名为ID),一个主键代表是否是草稿状态(字段名为Draft),同一ID标识的内容也就有了草稿和发布两个版本的内容。在国内,内容编辑人员和签发(即发布)人员通常不是同一个人,因此使用本设计也能满足国内环境下较为特殊的操作权限需求。

在EF中,一个实体设置多主键需要使用类似下面的语句:

this.HasKey(c => new { c.ID, c.Draft });

3、Entity Framework中的一对多和多对多的映射配置

国内的CMS通常都有个栏目的概念,其实质就是网站的组织结构。在本CMS中,文章、页面、文档等均能添加至栏目中。以文章为例,因为一篇文章可对应多个栏目、一个栏目下可能有多个文章,因此是N-N的关系,该N-N关系在Entity Framework中的文章Map配置如下:

 this.HasMany(p => p.CmsColumn).WithMany().Map(pc =>
{
pc.ToTable("YbCmsRelation");
pc.MapLeftKey(new string[] { "DataId", "Draft" });
pc.MapRightKey("RelatedId");
});

上述代码中,使用了一个关联表"YbCmsRelation",该表中的”DataId"和“Draft”对应文章的主键字段,“RelatedId"则对应栏目表(CmsColumn)中的主键字段,上述配置最大的优点是不会在文章实体和栏目实体中出现中间关联的导航属性,可直接跳过CmsRelation直接访问栏目,非常的方便。

至于一对多关系则简单得多,以问卷调查为例,一项调查有多个可选结果,可在“可选结果”一方的Map配置文件中进行如下示例配置并指明了“PollItemId”为外键:

//关联映射配置
this.HasRequired(t => t.CmsPollItem)
.WithMany(t => t.CmsPollAnswer)
.HasForeignKey(t => t.PollItemId)
.WillCascadeOnDelete(false);

4、文档的管理

文档管理主要是对上传的内容(文件、图片、音频、视频等)进行管理,本CMS中不仅需要对所有编辑器上传的内容进行集中管理,同时也需要支持草稿和发布两种状态、支持树形结构的管理等,因此本CMS对UEditor和KindEditor等编辑器默认提供的Controller进行了必要的重写,以让其适应新的需求;在具体过程中,还实现了对图片的优化处理,例如可按图片请求的尺寸生成对应尺寸的缩略图并进行缓存处理等。

生成缩略图的代码如下:

 public static Image GetThumbnail(Image img, int size)
{
// 生成缩略图
var bmp = new Bitmap(size, size);
using (var grp = Graphics.FromImage(bmp))
{
grp.SmoothingMode = SmoothingMode.HighQuality;
grp.CompositingQuality = CompositingQuality.HighQuality;
grp.InterpolationMode = InterpolationMode.High; // Resize and crop image
var dst = new Rectangle(, , bmp.Width, bmp.Height);
grp.DrawImage(img, dst, img.Width > img.Height ? (img.Width - img.Height)/ : ,
img.Height > img.Width ? (img.Height - img.Width)/ : , Math.Min(img.Width, img.Height),
Math.Min(img.Height, img.Width), GraphicsUnit.Pixel);
grp.Dispose();
}
return bmp;
}

生成缩略图

总结:目前国内.NET平台的 CMS 大部分均以 ASP.NET WebForm 为主,其实以CMS的特点来看,使用 ASP.NET MVC 进行开发无疑更加容易上手,开发和维护也更加方便和快捷。本CMS系统将在现有版本的基础之上提供更多符合国内使用习惯的功能和模块,具体可访问http://pjdemo.yellbuy.com/进一步了解。

YbSoftwareFactory 代码生成插件【二十二】:CMS基础功能的实现的更多相关文章

  1. YbSoftwareFactory 代码生成插件【十四】:通过 DynamicLinq 简单实现 N-Tier 部署下的服务端数据库通用分页

    YbSoftwareFactory 的 YbRapidSolution for WinForm 插件使用CSLA.NET作为业务层,CSLA.NET的一个强大的特性是支持 N-Tiers 部署.只需非 ...

  2. YbSoftwareFactory 代码生成插件【十五】:Show 一下最新的动态属性扩展功能与键值生成器功能

    YbSoftwareFactory 各种插件的基础类库中又新增了两个方便易用的功能:动态属性扩展与键值生成器,本章将分别介绍这两个非常方便的组件. 一.动态属性扩展 在实际的开发过程中,你肯定会遇到数 ...

  3. YbSoftwareFactory 代码生成插件【十九】:实体类配合数据库表字段进行属性扩展的小技巧

    实体类通常需要和数据库表进行了ORM映射,当你需要添加新的属性时,往往同时也需要在数据库中添加相应的字段并配置好映射关系,同时可能还需对数据访问组件进行重新编译和部署才能有效.而当你开始设计一个通用数 ...

  4. YbSoftwareFactory 代码生成插件【十八】:树形结构下的查询排序的数据库设计

    树形结构的排序在中国特色下十分普遍也非常重要,例如常说的五大班子,党委>人大>政府>政协>纪委,每个班子下还有部门,岗位,人员,最终排列的顺序通常需要按权力大小.重要性等进行排 ...

  5. YbSoftwareFactory 代码生成插件【十六】:Web 下灵活、强大的审批流程实现(含流程控制组件、流程设计器和表单设计器)

    程序=数据结构+算法,而企业级的软件=数据+流程,流程往往千差万别,客户自身有时都搞不清楚,随时变化的情况更是家常便饭,抛开功能等不谈,需求变化很大程度上就是流程的变化,流程的变化会给开发工作造成很大 ...

  6. Android开发(二十八)——基础功能函数

    /** * 判断事件是否在控件中 * * @param view * @param ev * @return * @see http://m.blog.csdn.net/blog/aygxylxk/8 ...

  7. Bootstrap <基础二十二>超大屏幕(Jumbotron)

    Bootstrap 支持的另一个特性,超大屏幕(Jumbotron).顾名思义该组件可以增加标题的大小,并为登陆页面内容添加更多的外边距(margin).使用超大屏幕(Jumbotron)的步骤如下: ...

  8. 二十六. Python基础(26)--类的内置特殊属性和方法

    二十六. Python基础(26)--类的内置特殊属性和方法 ● 知识框架 ● 类的内置方法/魔法方法案例1: 单例设计模式 # 类的魔法方法 # 案例1: 单例设计模式 class Teacher: ...

  9. 二十五. Python基础(25)--模块和包

    二十五. Python基础(25)--模块和包 ● 知识框架   ● 模块的属性__name__ # my_module.py   def fun1():     print("Hello& ...

随机推荐

  1. bzoj2500: 幸福的道路(树形dp+单调队列)

    好题.. 先找出每个节点的树上最长路 由树形DP完成 节点x,设其最长路的子节点为y 对于y的最长路,有向上和向下两种情况: down:y向子节点的最长路g[y][0] up:x的次长路的g[x][1 ...

  2. js--敏感词屏蔽

    <!doctype html><html><head><meta charset="utf-8"><meta name=&qu ...

  3. Unity学习疑问记录之Quaternion

    http://www.cnblogs.com/88999660/articles/2893126.html

  4. cuplayer酷播播放器 swf 带参数直接播放

    客户需要使用cuplayer,直接调用swf 播放器. /Player/player.swf?FlvID=745,此处写入视频ID; 官方给的例子,运行是有问题的. http://www.cuplay ...

  5. vb6保存项目到c盘的安装目录

    工程保存在安装目录("C:\Program Files (x86)\Microsoft Visual Studio\VB98\errhandler1.vbp")里. 文件管理器找不 ...

  6. openstack-glance

    1.glance 功能 对外提供image的管理功能 2.glance架构 api : REST API,提供对外调用接口 registry: 数据库管理逻辑处理 backen:image的实际存放位 ...

  7. MVC代码中如何调用api接口

    关于代码解释,为了方便读者浏览时更好理解代码的含义,我把注释都写在代码里面了.因为一开始我只考虑到功能上的实现并没有考虑代码的优化所以代码我就全写在一个页面了.至于那些生成扑克牌类.计算类等代码优化方 ...

  8. 掌握Thinkphp3.2.0----内置标签

    使用内置标签的时候,一定要注意闭合-----单标签自闭合,双标签对应闭合 标签的学习在于记忆和应用 一. 判断比较 //IF 语句的完整格式 <if condition="$user ...

  9. shell 条件判断

    一.数值判断 INT1 -eq INT2           INT1和INT2两数相等为真 INT1 -ne INT2           INT1和INT2两数不等为真 INT1 -gt INT2 ...

  10. slick for play 使用原生sql查询以及拼接sql

    在play中用函数式框架slick来操作数据库是一件很爽的事情.但有时因为某些特殊场景又不得不用原生的sql了. 还好slick支持这种写法,可以看看slick官方文档,Slick Plain SQL ...