简介:这份新闻发布系统数据库设计模板面向数据库开发者、DBA及项目管理者,用于快速搭建稳定可靠的新闻发布系统数据层,也可作为其他业务系统数据库设计的参考范本。资源包内含1个doc文档,整体约241KB,以正式设计报告形式组织,涵盖文档目的与范围、读者对象、术语解释、数据库环境说明、命名规则、逻辑设计、物理设计及表汇总等模块。文档中给出了用户信息表、新闻表、留言表、新闻类别表等核心表的结构与字段说明,并配有逻辑设计图与表汇总清单,便于读者直接对照建表、理解实体关系与索引规划。内容还涉及数据模型、规范化、存储、检索与安全等设计原则,适合需要规范数据库设计流程、撰写设计文档或进行课程实践的中级开发者参考。目前已有114人学习下载。
1. 新闻发布系统数据库设计模板:一份 2012 年的 SQL Server 建库底稿还能怎么用
翻到一份 2012 年的新闻发布系统数据库设计报告,第一反应不是"老",而是"全"。它把数据库环境、命名规则、逻辑设计、物理设计、安全性设计、与 ASP.NET 成员角色系统的对接,一条线全走完了。四张核心表——用户、新闻、留言、类别——加上一份数据字典,基本就是一个中小型内容站点的骨架。这份模板解决的不是"教你什么是数据库",而是"给你一份能直接照着建库、改字段、写脚本的底稿"。适合谁?正在做课程设计、毕业设计、或者接手一个老 ASP.NET 内容管理系统的开发者。SQL Server 2012 的语法今天在 2019/2022 上照样跑,PowerDesigner 的 ER 图思路也没过时。关键是,它把"为什么这么设计"和"字段怎么定"都写在了同一份文档里,这种完整度在现在的模板里反而少见。
2. 从文档到建库脚本:四张核心表怎么落地
2.1 先看环境约定,别急着写 CREATE TABLE
这份模板在动手建表之前,先把环境钉死了:Microsoft SQL Server Management Studio 建库维护,PowerDesigner 9.0 画 ER 图,查询分析器生成 news.sql 脚本,登录模式用混合身份验证,sa 账号,端口 1433。这些参数看着琐碎,但每一条都影响后面能不能顺利复现。
混合身份验证这个点值得单独说。SQL Server 默认装完是仅 Windows 身份验证,如果你直接拿模板里的 sa/123 去连,会报"用户 sa 登录失败"。常见做法是装完之后在服务器属性里把"服务器身份验证"改成"SQL Server 和 Windows 身份验证模式",然后重启服务,再去安全性里启用 sa 账号并设密码。端口 1433 是默认实例的默认端口,如果你装的是命名实例,端口是动态的,得去 SQL Server 配置管理器里看 TCP/IP 协议的实际端口。
命名规则这块,模板定的是:数据库名全小写英文,单词间下划线分割;表名用 dbo_ 前缀加首字母大写的表义名。但实际文档里表名写的是 dbo.News、dbo.Category 这种"架构名.表名"的格式,跟命名规则里说的 dbo_ 前缀有出入。这个矛盾不是笔误,而是 SQL Server 里 dbo 本身就是默认架构,写 dbo.News 等价于在 dbo 架构下建一张 News 表。我一般会统一成后者,因为 dbo_ 前缀在查询时反而要多写字符,而且跟系统表的命名习惯容易混。
2.2 四张表的字段设计,哪些能直接用,哪些要改
模板给出的表汇总很清晰:News 存新闻内容,Category 存类别,Comment 存留言,User 存管理员资料。逐张看字段。
用户信息表(dbo.User)的字段是 UserID(INT 主键)、UserName(VARCHAR 10)、UserCode(CHAR 20)、UserQQ(STRING)、UserAge(INT)、UserEmail(STRING)。这里有两个问题:一是 STRING 不是 SQL Server 的数据类型,得换成 NVARCHAR 或 VARCHAR;二是 UserName 只给 10 个字符,中文名勉强够,英文名加空格就紧张了。我一般会把 UserName 放到 NVARCHAR(50),UserCode 用 CHAR(20) 存加密后的密码倒是合理,因为哈希值长度固定。
新闻表(dbo.News)的字段是 NewsID(INT 主键)、NewsTitle(VARCHAR 100)、NewsContent(TEXT)、CreateTime(DATETIME)、CategoryID(INT)。TEXT 类型在 SQL Server 2005 之后就被标记为"不推荐使用"了,微软建议用 VARCHAR(MAX) 或 NVARCHAR(MAX) 替代。TEXT 类型不支持 LIKE 之外的很多字符串函数,而且不能直接跟某些排序规则配合。如果你在新版本上建库,直接把 TEXT 换成 NVARCHAR(MAX),存储中文新闻内容也更稳妥。
留言表(dbo.Comment)有个明显的设计问题:UserID 字段的描述写的是"用户 IP 地址",类型是 VARCHAR(15),但字段名却叫 UserID。这大概率是设计时的命名遗留——原本想存用户 ID,后来改成存 IP 了,但字段名没改。实际建表时我建议拆成两个字段:UserID(INT,可空,登录用户才填)和 UserIP(VARCHAR 45,兼容 IPv6)。VARCHAR(15) 只够 IPv4,现在 IPv6 地址最长 45 个字符,留够余量省得以后改表。
类别表(dbo.Category)最简单:CategoryID(INT 主键)、CategoryName(VARCHAR 20)、Type(VARCHAR 20)。Type 字段的描述是"新闻类别类",这个描述本身就很模糊。常见做法是把它当作类别分组或者排序权重用,如果只是单纯的新闻分类,这个字段可以砍掉,减少冗余。
2.3 建库脚本:从模板到可执行 SQL
把上面的修正落到脚本里,下面这段可以直接在 SSMS 里跑。注意我保留了模板的命名风格,但修正了数据类型和字段长度。
-- 创建数据库,字符集用 Chinese_PRC_CI_AS 兼容中文 CREATE DATABASE news COLLATE Chinese_PRC_CI_AS; GO USE news; GO -- 用户信息表:UserCode 存加密后的密码,固定长度用 CHAR CREATE TABLE dbo.[User] ( UserID INT IDENTITY(1,1) NOT NULL PRIMARY KEY, UserName NVARCHAR(50) NOT NULL, UserCode CHAR(64) NOT NULL, -- SHA-256 哈希值 64 位 UserQQ NVARCHAR(20) NULL, UserAge INT NULL, UserEmail NVARCHAR(100) NULL ); GO -- 新闻类别表:Type 保留作为分组标识 CREATE TABLE dbo.Category ( CategoryID INT IDENTITY(1,1) NOT NULL PRIMARY KEY, CategoryName NVARCHAR(50) NOT NULL, Type NVARCHAR(20) NULL ); GO -- 新闻表:TEXT 换成 NVARCHAR(MAX),加外键约束 CREATE TABLE dbo.News ( NewsID INT IDENTITY(1,1) NOT NULL PRIMARY KEY, NewsTitle NVARCHAR(200) NOT NULL, NewsContent NVARCHAR(MAX) NOT NULL, CreateTime DATETIME NOT NULL DEFAULT GETDATE(), CategoryID INT NOT NULL, CONSTRAINT FK_News_Category FOREIGN KEY (CategoryID) REFERENCES dbo.Category(CategoryID) ); GO -- 留言表:拆出 UserIP,UserID 可空 CREATE TABLE dbo.Comment ( CommentID INT IDENTITY(1,1) NOT NULL PRIMARY KEY, CommentContent NVARCHAR(MAX) NOT NULL, CreateTime DATETIME NOT NULL DEFAULT GETDATE(), UserID INT NULL, UserIP VARCHAR(45) NULL, NewsID INT NOT NULL, CONSTRAINT FK_Comment_News FOREIGN KEY (NewsID) REFERENCES dbo.News(NewsID), CONSTRAINT FK_Comment_User FOREIGN KEY (UserID) REFERENCES dbo.[User](UserID) ); GO这段脚本跟模板的差异集中在三处:一是所有 STRING 换成 NVARCHAR 并调整了长度;二是 TEXT 换成 NVARCHAR(MAX);三是补上了模板里没写但逻辑上必须有的外键约束。模板的物理设计部分只列了字段,没提外键,但 News.CategoryID 和 Comment.NewsID 如果不加约束,删类别的时候会留下孤儿新闻,查留言的时候会关联到不存在的新闻。加外键的代价是插入顺序有要求——必须先插 Category 再插 News,先插 News 再插 Comment。
2.4 索引和查询:模板没写但绕不开的一步
模板的物理设计只到表结构为止,索引一个字没提。但新闻发布系统最典型的查询是"按类别查最新新闻"和"按新闻查留言",这两个场景不加索引,数据量一上来就是全表扫描。
-- 新闻表:按类别+发布时间倒序查最新,建复合索引 CREATE NONCLUSTERED INDEX IX_News_Category_Time ON dbo.News (CategoryID, CreateTime DESC); GO -- 留言表:按新闻 ID 查留言,建索引 CREATE NONCLUSTERED INDEX IX_Comment_NewsID ON dbo.Comment (NewsID); GO复合索引的字段顺序有讲究。CategoryID 在前、CreateTime 在后,是因为查询条件里 CategoryID 是等值匹配,CreateTime 是范围排序,等值列放前面能让索引更快定位。如果你把顺序反过来,按类别筛选时索引基本用不上。这个细节模板里没有,但实际跑起来差别很大——几万条新闻的时候,有没有这个索引就是秒级和分钟级的区别。
3. 安全性设计与 ASP.NET 成员系统的对接坑
3.1 密码加密:模板说了要做,但没说怎么做
模板在安全性设计里明确写了"存储在表中的密码字段值不能以明文显示,应做适当的加密后再存入数据库"。这句话是对的,但没给方案。2012 年那会儿常见做法是 MD5 或 SHA1,现在这两个都已经不安全了。如果你是在维护老系统,可能得兼容已有的 MD5 哈希;如果是新做,直接用 SHA-256 加盐。
// ASP.NET 里生成 SHA-256 加盐哈希的常见写法 using System.Security.Cryptography; using System.Text; public static string HashPassword(string password, string salt) { using (var sha = SHA256.Create()) { // 盐拼在密码前面,避免彩虹表直接命中 var bytes = Encoding.UTF8.GetBytes(salt + password); var hash = sha.ComputeHash(bytes); return BitConverter.ToString(hash).Replace("-", "").ToLower(); } }盐值建议每个用户独立生成,存在 User 表里加一个 Salt 字段。如果所有用户共用一个盐,等于没加。模板里 UserCode 是 CHAR(20),只够存 MD5 的 32 位十六进制截断,SHA-256 是 64 位十六进制,所以字段长度得改。这也是为什么我在建表脚本里把 UserCode 设成了 CHAR(64)。
3.2 跟 ASP.NET 成员系统的字段映射
模板第 7 章讲了一个很具体的对接方案:用 BBC_EMPLOYEE 表的 E_NAME 字段和 ASPNET_USERS 表的 UserName 字段共同保存用户编号,用 E_PASSWORD 和 aspnet_Membership 的 Password 字段共同保存密码。这个设计的意思是,业务表里存一份用户信息,ASP.NET 成员系统里也存一份,两边通过用户名字段关联。
这种"双写"方案在当年很常见,因为 ASP.NET 的成员系统开箱即用,但它的表结构是固定的,你没法直接往里加业务字段。所以做法是:注册的时候往两张表各写一条,登录的时候用成员系统验证,验证通过后再去业务表查详细信息。
坑在于数据一致性。如果注册时成员系统写成功了但业务表写失败了,就会出现"能登录但查不到用户资料"的情况。常见做法是把两张表的写入包在一个事务里,或者干脆放弃成员系统,自己实现一套简单的用户表。如果你现在接手的是这种老系统,排查用户问题时第一件事就是对比 ASPNET_USERS 和业务表里的记录数是否一致。
3.3 数据库连接与权限隔离
模板提到"游客只能通过客户端软件访问数据库表中的数据",但实际实施时"把数据库服务和 WEB 服务安装到了同一个服务器上"。这个妥协很真实——小项目没条件做物理隔离。但即便在同一台服务器上,也不应该让 Web 应用直接用 sa 账号连数据库。
sa 是超级管理员,能删库、能改系统配置。Web 应用的连接字符串如果泄露,等于把整个数据库交出去了。正确做法是给每个应用建独立的登录名和用户,只授予必要的权限:
-- 创建应用专用登录名 CREATE LOGIN news_app WITH PASSWORD = 'StrongPassword123!'; GO USE news; GO -- 创建数据库用户并映射到登录名 CREATE USER news_app FOR LOGIN news_app; GO -- 只授予新闻和留言表的增删改查,不给 DDL 权限 GRANT SELECT, INSERT, UPDATE, DELETE ON dbo.News TO news_app; GRANT SELECT, INSERT, UPDATE, DELETE ON dbo.Comment TO news_app; GRANT SELECT ON dbo.Category TO news_app; GRANT SELECT, INSERT, UPDATE ON dbo.[User] TO news_app; GO这样即使连接字符串泄露,攻击者也只能操作这几张表的数据,删不了库、改不了表结构。模板里 sa/123 这种配置,放在内网测试环境凑合,一旦上线就是血泪教训。
4. 避坑与排查:这份模板实际用起来会遇到的五个问题
4.1 现象:按模板建表时报"数据类型 STRING 无效"
原因:模板里 UserQQ、UserEmail 等字段写的是 STRING,但 SQL Server 没有这个类型。这是从 PowerDesigner 的概念模型直接导出时没做类型映射导致的。
解决:把所有 STRING 替换成 NVARCHAR,长度按实际需求定。QQ 号用 NVARCHAR(20),邮箱用 NVARCHAR(100)。如果是从 PowerDesigner 生成脚本,在 DBMS 设置里把 STRING 映射到 VARCHAR 或 NVARCHAR。
4.2 现象:插入中文新闻标题后显示乱码
原因:数据库的排序规则不是中文排序规则,或者字段类型用了 VARCHAR 而不是 NVARCHAR。VARCHAR 按字节存储,中文占两个字节,长度不够就截断;排序规则不对则显示问号。
解决:建库时指定 COLLATE Chinese_PRC_CI_AS,所有存中文的字段用 NVARCHAR。已经建好的库可以用 ALTER DATABASE 改排序规则,但已有数据的字段需要逐列改类型。
4.3 现象:删除新闻类别后,新闻列表里出现空白项
原因:News 表的 CategoryID 没有外键约束,删了 Category 里的记录,News 里还留着对应的 CategoryID,关联查询时查不到类别名。
解决:加外键约束并设置级联删除,或者在应用层做软删除——Category 表加 IsDeleted 字段,删除时只标记不物理删除。级联删除要谨慎,删一个类别会连带删掉该类别下所有新闻,通常不是想要的结果。
4.4 现象:留言表里 UserID 存的是 IP 地址,按用户查留言查不到
原因:模板里 UserID 字段的描述是"用户 IP 地址",字段名和实际用途不一致。如果应用代码按 UserID 当整数处理,存 IP 字符串会报类型转换错误;如果当字符串处理,又没法跟 User 表关联。
解决:拆成 UserID(INT,可空)和 UserIP(VARCHAR(45))两个字段。登录用户的留言填 UserID,游客留言填 UserIP。查询"我的留言"时用 UserID 关联,查询"某条新闻的留言"时两个字段都查。
4.5 现象:ASP.NET 成员系统能登录,但业务页面报"用户不存在"
原因:成员系统和业务表双写不一致。注册时成员系统写入成功,业务表写入失败(比如事务回滚、连接超时),导致两边的用户记录数不匹配。
解决:先查 ASPNET_USERS 表里有多少条记录,再查业务表里有多少条,对比差异。修复方式是写一个补数据脚本,把成员系统里有但业务表里没有的用户补进去。长期方案是改用单一用户表,或者用数据库触发器保证双写一致。
5. 把模板变成可维护的建库流程:我的三个习惯
这份模板最大的价值不是那四张表的字段定义,而是它展示了一个完整的数据库设计文档应该包含什么:环境说明、命名规则、逻辑设计、物理设计、安全性设计、对接方案。字段可以改,但这个结构值得保留。
我现在拿到任何一份数据库设计模板,第一件事是把它转成可执行的迁移脚本,而不是直接在 SSMS 里手点建表。手点的表结构没法版本管理,换台机器就得重来一遍。用脚本的话,配合 Flyway 或 DbUp 这类工具,每次改表结构都留一条迁移记录,出问题能回滚。
# 用 DbUp 做迁移的典型目录结构 # /migrations # 001_create_database.sql # 002_create_user_table.sql # 003_create_news_table.sql # 004_add_indexes.sql # 每次改表结构新增一个编号文件,不修改已执行过的文件第二个习惯是给每张表加 CreatedAt 和 UpdatedAt 两个审计字段。模板里的表只有 CreateTime,没有更新时间。新闻改了标题、留言被编辑过,这些操作如果没有 UpdatedAt,排查问题时根本不知道数据是什么时候变的。加这两个字段的成本很低,但省下的排查时间很值。
-- 给已有表补审计字段 ALTER TABLE dbo.News ADD UpdatedAt DATETIME NULL; GO -- 建触发器自动更新 UpdatedAt CREATE TRIGGER TR_News_UpdateTime ON dbo.News AFTER UPDATE AS BEGIN SET NOCOUNT ON; UPDATE dbo.News SET UpdatedAt = GETDATE() FROM dbo.News n INNER JOIN inserted i ON n.NewsID = i.NewsID; END GO第三个习惯是每次改完表结构,立刻用模板里的"表汇总"格式更新文档。字段名、类型、长度、是否可空、默认值、备注,一个不落。这份 2012 年的模板之所以现在还能用,就是因为它的数据字典写得足够细。我见过太多项目,表建完就没人维护文档了,半年后连自己都不敢改字段。从那以后我每次动表结构,都强制走一遍"改脚本→跑迁移→更新文档→提交版本库"这四步,少一步都不行。
希望这份拆解能帮你把这份老模板用出新价值。
本文还有配套的精品资源,点击获取