下面是一个简单的数据库模型设计,包括以下表:
文章表(article):存储博客文章的信息,包括文章ID、文章标题、文章内容、发布时间、最后修改时间、文章所属分类ID等字段。
评论表(comment):存储博客文章的评论信息,包括评论ID、评论内容、评论时间、评论人昵称、评论人邮箱、评论人网址、评论所属文章ID等字段。
分类表(category):存储博客文章的分类信息,包括分类ID、分类名称等字段。
标签表(tag):存储博客文章的标签信息,包括标签ID、标签名称等字段。
文章-分类关联表(article_category):存储文章和分类之间的关联关系,包括文章ID和分类ID两个字段。
文章-标签关联表(article_tag):存储文章和标签之间的关联关系,包括文章ID和标签ID两个字段。
下面是数据库模型设计的SQL语句:
CREATE TABLE `article` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '文章ID',
`title` varchar(255) DEFAULT NULL COMMENT '文章标题',
`content` longtext COMMENT '文章内容',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
`category_id` int(11) DEFAULT NULL COMMENT '文章所属分类ID',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文章表';
CREATE TABLE `comment` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '评论ID',
`content` longtext COMMENT '评论内容',
`create_time` datetime DEFAULT NULL COMMENT '评论时间',
`nickname` varchar(255) DEFAULT NULL COMMENT '评论人昵称',
`email` varchar(255) DEFAULT NULL COMMENT '评论人邮箱',
`website` varchar(255) DEFAULT NULL COMMENT '评论人网址',
`article_id` int(11) DEFAULT NULL COMMENT '评论所属文章ID',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='评论表';
CREATE TABLE `category` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '分类ID',
`name` varchar(255) DEFAULT NULL COMMENT '分类名称',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分类表';
CREATE TABLE `tag` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '标签ID',
`name` varchar(255) DEFAULT NULL COMMENT '标签名称',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='标签表';
CREATE TABLE `article_category` (
`article_id` int(11) NOT NULL COMMENT '文章ID',
`category_id` int(11) NOT NULL COMMENT '分类ID',
PRIMARY KEY (`article_id`,`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文章分类关联表';
CREATE TABLE `article_tag` (
`article_id` int(11) NOT NULL COMMENT '文章ID',
`tag_id` int(11) NOT NULL COMMENT '标签ID',
PRIMARY KEY (`article_id`,`tag_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文章标签关联表';
以上是一个简单的博客数据库模型设计,可以根据实际需求进行调整和优化。
下面是使用Spring Boot框架搭建后端环境的步骤:
下面是安装Java开发环境的步骤:
1. 下载JDK安装包:在Oracle官网下载对应操作系统的JDK安装包([https://www.oracle.com/java/technologies/javase-downloads.html ↗](https://www.oracle.com/java/technologies/javase-downloads.html))。
2. 安装JDK:运行下载的JDK安装包,按照提示完成JDK的安装。如果是Windows系统,可以选择默认安装路径。
3. 配置环境变量:将JDK的安装路径配置到系统环境变量中,包括JAVA_HOME、CLASSPATH、PATH三个环境变量。
- JAVA_HOME:JDK的安装路径,例如:C:\Program Files\Java\jdk1.8.0_221
- CLASSPATH:Java类库搜索路径,一般设置为当前目录,例如:.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar
- PATH:系统执行命令的搜索路径,需要在原有的路径后面添加JDK的bin目录,例如:%PATH%;%JAVA_HOME%\bin
4. 验证JDK安装:打开命令行窗口,输入java -version命令,查看是否输出了JDK版本信息,如果输出了,则说明JDK安装成功。
安装Java开发环境可能会因操作系统不同而略有差异,以上步骤仅供参考。
下面是安装Maven的步骤:
1. 下载Maven安装包:在Apache官网下载最新版本的Maven安装包([https://maven.apache.org/download.cgi ↗](https://maven.apache.org/download.cgi))。
2. 解压Maven安装包:将下载的Maven安装包解压到任意目录下,例如:C:\apache-maven-3.8.3。
3. 配置环境变量:将Maven的bin目录添加到系统环境变量的PATH中,例如:%PATH%;C:\apache-maven-3.8.3\bin。
4. 验证Maven安装:打开命令行窗口,输入mvn -v命令,查看是否输出了Maven版本信息,如果输出了,则说明Maven安装成功。
安装Maven可能会因操作系统不同而略有差异,以上步骤仅供参考。
下面是使用Spring Initializr创建Spring Boot项目的步骤:
1. 打开Spring Initializr:在浏览器中打开Spring Initializr([https://start.spring.io/ ↗](https://start.spring.io/))。
2. 选择项目配置:选择Maven项目、选择相应的Spring Boot版本、选择Web、MyBatis等相关依赖,可以根据实际需求进行选择。
3. 生成项目:点击“Generate”按钮,生成项目的压缩包。
4. 解压项目:将生成的项目压缩包解压到任意目录下,例如:C:\springboot-project。
5. 导入项目:使用IDEA等开发工具,导入解压后的项目,即可开始开发。
创建Spring Boot项目的具体步骤可能会因开发工具不同而略有差异,以上步骤仅供参考。
下面是在Spring Boot项目中配置数据库连接的步骤:
在项目的src/main/resources目录下创建application.properties或application.yml文件,用于存储配置信息。
在配置文件中添加数据库连接信息,例如:
# MySQL数据库连接信息
spring.datasource.url=jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
```
其中,spring.datasource.url属性指定数据库的URL,spring.datasource.username和spring.datasource.password属性指定数据库的用户名和密码,spring.datasource.driver-class-name属性指定数据库驱动的类名。
保存配置文件,重启项目,即可使用配置的数据库连接信息连接到相应的数据库。
以上是在Spring Boot项目中配置数据库连接的基本步骤,可以根据实际需求进行调整和优化。
下面是在Spring Boot项目中配置MyBatis的步骤:
在项目的src/main/resources目录下创建mybatis-config.xml文件,用于存储MyBatis的配置信息。
在配置文件中添加MyBatis配置信息,例如:
```
其中,settings节点中可以配置MyBatis的各种设置,例如是否启用缓存、是否启用延迟加载等。
在配置文件中添加Mapper文件的路径信息,例如:
mybatis.mapper-locations=classpath:mapper/*.xml
其中,mybatis.mapper-locations属性指定Mapper文件的路径,可以使用通配符指定多个Mapper文件。
在Spring Boot的配置文件中添加MyBatis相关的配置信息,例如:
# MyBatis配置文件路径
mybatis.config-location=classpath:mybatis-config.xml
其中,mybatis.config-location属性指定MyBatis的配置文件路径。
在Mapper接口中使用@Mapper注解,告诉Spring Boot需要将该接口注册为Mapper。
在Mapper XML文件中编写SQL语句,实现与数据库的交互操作。
以上是在Spring Boot项目中配置MyBatis的基本步骤,可以根据实际需求进行调整和优化。
下面是编写实体类的基本步骤:
根据数据库模型设计,确定实体类的属性和对应的数据库表字段。
在项目的src/main/java目录下创建实体类的包,例如com.example.demo.entity。
在实体类的包中创建与数据库表对应的实体类,例如User类。
在实体类中添加属性和对应的getter/setter方法,例如:
public class User {
private Long id;
private String name;
private Integer age;
// 省略getter/setter方法
}
````
其中,id、name、age属性对应数据库表中的id、name、age字段。
(可选)使用注解或XML方式实现实体类和数据库表之间的映射关系,例如:
使用注解方式:
@TableName("user")
public class User {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@TableField("name")
private String name;
@TableField("age")
private Integer age;
// 省略getter/setter方法
}
使用XML方式:
以上是编写实体类的基本步骤,可以根据实际需求进行调整和优化。
编写Mapper类,需要分别编写Mapper接口和Mapper XML文件,实现与数据库的交互操作,包括增删改查等。
以下是一个可能的示例:
public interface ArticleMapper {
// 根据ID查询文章信息及其所属分类和标签信息
Article findArticleById(int id);
// 查询所有文章信息及其所属分类和标签信息
List<Article> findAllArticles();
// 新增文章信息
int addArticle(Article article);
// 更新文章信息
int updateArticle(Article article);
// 删除文章信息
int deleteArticle(int id);
}
<mapper namespace="com.example.mapper.ArticleMapper">
<select id="findArticleById" resultType="com.example.bean.Article">
SELECT a.id, a.title, a.content, a.create_time, a.update_time, c.id AS category_id, c.name AS category_name,
GROUP_CONCAT(t.id ORDER BY t.id SEPARATOR ',') AS tag_ids, GROUP_CONCAT(t.name ORDER BY t.id SEPARATOR ',') AS tag_names
FROM article a
LEFT JOIN article_category ac ON a.id = ac.article_id
LEFT JOIN category c ON ac.category_id = c.id
LEFT JOIN article_tag at ON a.id = at.article_id
LEFT JOIN tag t ON at.tag_id = t.id
WHERE a.id = #{id}
select>
<select id="findAllArticles" resultType="com.example.bean.Article">
SELECT a.id, a.title, a.content, a.create_time, a.update_time, c.id AS category_id, c.name AS category_name,
GROUP_CONCAT(t.id ORDER BY t.id SEPARATOR ',') AS tag_ids, GROUP_CONCAT(t.name ORDER BY t.id SEPARATOR ',') AS tag_names
FROM article a
LEFT JOIN article_category ac ON a.id = ac.article_id
LEFT JOIN category c ON ac.category_id = c.id
LEFT JOIN article_tag at ON a.id = at.article_id
LEFT JOIN tag t ON at.tag_id = t.id
GROUP BY a.id
select>
<insert id="addArticle" parameterType="com.example.bean.Article">
INSERT INTO article(title, content, create_time, update_time)
VALUES(#{title}, #{content}, #{createTime}, #{updateTime})
insert>
<insert id="addArticleCategory" parameterType="java.util.Map">
INSERT INTO article_category(article_id, category_id)
VALUES(#{articleId}, #{categoryId})
insert>
<insert id="addArticleTag" parameterType="java.util.Map">
INSERT INTO article_tag(article_id, tag_id)
VALUES(#{articleId}, #{tagId})
insert>
<update id="updateArticle" parameterType="com.example.bean.Article">
UPDATE article
SET title = #{title}, content = #{content}, update_time = #{updateTime}
WHERE id = #{id}
update>
<delete id="deleteArticleCategory" parameterType="int">
DELETE FROM article_category WHERE article_id = #{id}
delete>
<delete id="deleteArticleTag" parameterType="int">
DELETE FROM article_tag WHERE article_id = #{id}
delete>
<delete id="deleteArticle" parameterType="int">
DELETE FROM article WHERE id = #{id}
delete>
mapper>
上述示例代码中,Mapper接口中定义了增删改查等方法,对应的Mapper XML文件中定义了对应的SQL语句,使用#{}占位符来引用方法参数或实体类属性,使用resultType指定查询结果的返回类型。同时,为了处理多对多关系,SQL语句中使用了LEFT JOIN和GROUP_CONCAT等关键字,将文章与分类、标签等信息查询出来,并使用Map参数来实现多表数据的插入操作。
编写Service类,需要实现业务逻辑的处理,并调用Mapper类中的方法实现数据的读写操作。
以下是一个可能的示例:
@Service
public class ArticleService {
@Autowired
private ArticleMapper articleMapper;
@Autowired
private CategoryMapper categoryMapper;
@Autowired
private TagMapper tagMapper;
// 新增文章信息
public boolean addArticle(Article article, List<Integer> categoryIds, List<Integer> tagIds) {
// 新增文章信息
int rows = articleMapper.addArticle(article);
if (rows <= 0) {
return false;
}
// 新增文章分类关联信息
for (Integer categoryId : categoryIds) {
Map<String, Integer> map = new HashMap<>();
map.put("articleId", article.getId());
map.put("categoryId", categoryId);
articleMapper.addArticleCategory(map);
}
// 新增文章标签关联信息
for (Integer tagId : tagIds) {
Map<String, Integer> map = new HashMap<>();
map.put("articleId", article.getId());
map.put("tagId", tagId);
articleMapper.addArticleTag(map);
}
return true;
}
// 更新文章信息
public boolean updateArticle(Article article, List<Integer> categoryIds, List<Integer> tagIds) {
// 更新文章信息
int rows = articleMapper.updateArticle(article);
if (rows <= 0) {
return false;
}
// 删除文章分类关联信息
articleMapper.deleteArticleCategory(article.getId());
// 新增文章分类关联信息
for (Integer categoryId : categoryIds) {
Map<String, Integer> map = new HashMap<>();
map.put("articleId", article.getId());
map.put("categoryId", categoryId);
articleMapper.addArticleCategory(map);
}
// 删除文章标签关联信息
articleMapper.deleteArticleTag(article.getId());
// 新增文章标签关联信息
for (Integer tagId : tagIds) {
Map<String, Integer> map = new HashMap<>();
map.put("articleId", article.getId());
map.put("tagId", tagId);
articleMapper.addArticleTag(map);
}
return true;
}
// 删除文章信息
public boolean deleteArticle(int id) {
// 删除文章信息
int rows = articleMapper.deleteArticle(id);
if (rows <= 0) {
return false;
}
// 删除文章分类关联信息
articleMapper.deleteArticleCategory(id);
// 删除文章标签关联信息
articleMapper.deleteArticleTag(id);
return true;
}
// 根据ID查询文章信息及其所属分类和标签信息
public Article findArticleById(int id) {
Article article = articleMapper.findArticleById(id);
if (article != null) {
// 查询文章所属的分类信息
List<Category> categories = categoryMapper.findCategoriesByArticleId(article.getId());
article.setCategories(categories);
// 查询文章所属的标签信息
List<Tag> tags = tagMapper.findTagsByArticleId(article.getId());
article.setTags(tags);
}
return article;
}
// 查询所有文章信息及其所属分类和标签信息
public List<Article> findAllArticles() {
List<Article> articles = articleMapper.findAllArticles();
if (articles != null && !articles.isEmpty()) {
for (Article article : articles) {
// 查询文章所属的分类信息
List<Category> categories = categoryMapper.findCategoriesByArticleId(article.getId());
article.setCategories(categories);
// 查询文章所属的标签信息
List<Tag> tags = tagMapper.findTagsByArticleId(article.getId());
article.setTags(tags);
}
}
return articles;
}
}
上述示例代码中,ArticleService类中注入了ArticleMapper、CategoryMapper和TagMapper等Mapper类,通过调用Mapper类中的方法实现对数据库的增删改查等操作,并在此基础上实现了业务逻辑的处理。在新增和更新文章信息时,通过调用articleMapper的addArticleCategory和addArticleTag方法,新增文章与分类、标签之间的关联信息;在更新文章信息时,先删除原有的分类、标签关联信息,再新增新的分类、标签关联信息;在删除文章信息时,同样需要删除文章与分类、标签之间的关联信息。在查询文章信息时,通过调用categoryMapper和tagMapper的方法查询文章所属的分类和标签信息,并将查询结果设置到Article对象的categories和tags属性中,返回给调用方。
编写Controller类:编写Controller类,处理HTTP请求,调用Service类中的方法返回相应的数据。
启动项目:使用IDEA等开发工具启动项目,测试接口是否正常。
编写Controller类,处理HTTP请求,调用Service类中的方法返回相应的数据。
以下是一个可能的示例:
@RestController
@RequestMapping("/articles")
public class ArticleController {
@Autowired
private ArticleService articleService;
// 新增文章信息
@PostMapping("")
public ResponseEntity<?> addArticle(@RequestBody Article article, @RequestParam List<Integer> categoryIds, @RequestParam List<Integer> tagIds) {
boolean result = articleService.addArticle(article, categoryIds, tagIds);
if (result) {
return ResponseEntity.ok().build();
} else {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
// 更新文章信息
@PutMapping("/{id}")
public ResponseEntity<?> updateArticle(@PathVariable int id, @RequestBody Article article, @RequestParam List<Integer> categoryIds, @RequestParam List<Integer> tagIds) {
article.setId(id);
boolean result = articleService.updateArticle(article, categoryIds, tagIds);
if (result) {
return ResponseEntity.ok().build();
} else {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
// 删除文章信息
@DeleteMapping("/{id}")
public ResponseEntity<?> deleteArticle(@PathVariable int id) {
boolean result = articleService.deleteArticle(id);
if (result) {
return ResponseEntity.ok().build();
} else {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
// 根据ID查询文章信息及其所属分类和标签信息
@GetMapping("/{id}")
public ResponseEntity<?> findArticleById(@PathVariable int id) {
Article article = articleService.findArticleById(id);
if (article != null) {
return ResponseEntity.ok(article);
} else {
return ResponseEntity.notFound().build();
}
}
// 查询所有文章信息及其所属分类和标签信息
@GetMapping("")
public ResponseEntity<?> findAllArticles() {
List<Article> articles = articleService.findAllArticles();
return ResponseEntity.ok(articles);
}
}
上述示例代码中,ArticleController类处理了HTTP请求,并通过调用ArticleService类中的方法返回相应的数据。在新增、更新和删除文章信息时,通过@RequestBody注解将请求体中的JSON数据转化为Article对象,并通过@RequestParam注解获取请求参数中的categoryIds和tagIds,传递给ArticleService类中的方法进行处理。在查询文章信息时,通过@PathParm注解获取请求参数中的文章ID,并通过调用ArticleService类中的方法获取文章信息。最后,通过ResponseEntity对象返回HTTP响应,其中ok()方法返回状态码200,notFound()方法返回状态码404。
以上是使用Spring Boot框架搭建后端环境的基本步骤,可以根据实际需求进行调整和优化。
代码生成器可以根据数据库表结构自动生成Model、Mapper、Service、Controller等类的代码,提高开发效率和代码质量。以下是一个可能的示例:
public class CodeGenerator {
private static final String DRIVER_CLASS_NAME = "com.mysql.cj.jdbc.Driver";
private static final String DATABASE_URL = "jdbc:mysql://localhost:3306/test?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8";
private static final String USER_NAME = "root";
private static final String PASSWORD = "123456";
private static final String BASE_PACKAGE = "com.example.demo";
private static final String MAPPER_PACKAGE = "mapper";
private static final String SERVICE_PACKAGE = "service";
private static final String CONTROLLER_PACKAGE = "controller";
private static final String MODEL_PACKAGE = "model";
private static final String MAPPER_SUFFIX = "Mapper";
private static final String SERVICE_SUFFIX = "Service";
private static final String CONTROLLER_SUFFIX = "Controller";
private static final String MODEL_SUFFIX = "";
public static void main(String[] args) throws Exception {
// 创建FreeMarker配置对象
Configuration cfg = new Configuration(Configuration.VERSION_2_3_28);
// 设置FreeMarker模板文件所在目录
cfg.setClassForTemplateLoading(CodeGenerator.class, "/templates");
// 初始化数据源
DataSource dataSource = createDataSource();
// 获取数据库中所有的表名
List<String> tableNames = getTableNames(dataSource);
// 循环处理每个表
for (String tableName : tableNames) {
// 获取表的元数据信息
List<ColumnMetaData> columnMetaDatas = getTableMetaData(dataSource, tableName);
// 生成Mapper接口文件
generateMapper(cfg, tableName, columnMetaDatas);
// 生成Model类文件
generateModel(cfg, tableName, columnMetaDatas);
// 生成Service类文件
generateService(cfg, tableName, columnMetaDatas);
// 生成Controller类文件
generateController(cfg, tableName, columnMetaDatas);
}
}
// 创建数据源
private static DataSource createDataSource() {
HikariConfig config = new HikariConfig();
config.setDriverClassName(DRIVER_CLASS_NAME);
config.setJdbcUrl(DATABASE_URL);
config.setUsername(USER_NAME);
config.setPassword(PASSWORD);
HikariDataSource dataSource = new HikariDataSource(config);
return dataSource;
}
// 获取所有表名
private static List<String> getTableNames(DataSource dataSource) throws SQLException {
List<String> tableNames = new ArrayList<>();
try (Connection connection = dataSource.getConnection()) {
DatabaseMetaData metaData = connection.getMetaData();
ResultSet resultSet = metaData.getTables(null, null, null, new String[]{"TABLE"});
while (resultSet.next()) {
String tableName = resultSet.getString("TABLE_NAME");
tableNames.add(tableName);
}
}
return tableNames;
}
// 获取表的元数据信息
private static List<ColumnMetaData> getTableMetaData(DataSource dataSource, String tableName) throws SQLException {
List<ColumnMetaData> columnMetaDatas = new ArrayList<>();
try (Connection connection = dataSource.getConnection()) {
DatabaseMetaData metaData = connection.getMetaData();
ResultSet resultSet = metaData.getColumns(null, null, tableName, null);
while (resultSet.next()) {
String columnName = resultSet.getString("COLUMN_NAME");
String columnType = resultSet.getString("TYPE_NAME");
String columnComment = resultSet.getString("REMARKS");
boolean isNullable = resultSet.getBoolean("NULLABLE");
columnMetaDatas.add(new ColumnMetaData(columnName, columnType, columnComment, isNullable));
}
}
return columnMetaDatas;
}
// 生成Mapper接口文件
private static void generateMapper(Configuration cfg, String tableName, List<ColumnMetaData> columnMetaDatas) throws IOException, TemplateException {
Map<String, Object> model = new HashMap<>();
model.put("package", BASE_PACKAGE + "." + MAPPER_PACKAGE);
model.put("tableName", tableName);
model.put("modelName", toCamelCase(tableName, true) + MODEL_SUFFIX);
model.put("columns", columnMetaDatas);
String fileName = toCamelCase(tableName, true) + MAPPER_SUFFIX + ".java";
generateFile(cfg, model, "mapper.ftl", fileName);
}
// 生成Model类文件
private static void generateModel(Configuration cfg, String tableName, List<ColumnMetaData> columnMetaDatas) throws IOException, TemplateException {
Map<String, Object> model = new HashMap<>();
model.put("package", BASE_PACKAGE + "." + MODEL_PACKAGE);
model.put("tableName", tableName);
model.put("modelName", toCamelCase(tableName, true) + MODEL_SUFFIX);
model.put("columns", columnMetaDatas);
String fileName = toCamelCase(tableName, true) + MODEL_SUFFIX + ".java";
generateFile(cfg, model, "model.ftl", fileName);
}
// 生成Service类文件
private static void generateService(Configuration cfg, String tableName, List<ColumnMetaData> columnMetaDatas) throws IOException, TemplateException {
Map<String, Object> model = new HashMap<>();
model.put("package", BASE_PACKAGE + "." + SERVICE_PACKAGE);
model.put("tableName", tableName);
model.put("modelName", toCamelCase(tableName, true) + MODEL_SUFFIX);
model.put("mapperName", toCamelCase(tableName, true) + MAPPER_SUFFIX);
model.put("serviceSuffix", SERVICE_SUFFIX);
model.put("columns", columnMetaDatas);
String fileName = toCamelCase(tableName, true) + SERVICE_SUFFIX + ".java";
generateFile(cfg, model, "service.ftl", fileName);
}
// 生成Controller类文件
private static void generateController(Configuration cfg, String tableName, List<ColumnMetaData> columnMetaDatas) throws IOException, TemplateException {
Map<String, Object> model = new HashMap<>();
model.put("package", BASE_PACKAGE + "." + CONTROLLER_PACKAGE);
model.put("tableName", tableName);
model.put("modelName", toCamelCase(tableName, true) + MODEL_SUFFIX);
model.put("serviceName", toCamelCase(tableName, true) + SERVICE_SUFFIX);
model.put("controllerSuffix", CONTROLLER_SUFFIX);
model.put("columns", columnMetaDatas);
String fileName = toCamelCase(tableName, true) + CONTROLLER_SUFFIX + ".java";
generateFile(cfg, model, "controller.ftl", fileName);
}
// 生成文件
private static void generateFile(Configuration cfg, Map<String, Object> model, String templateFileName, String outputFileName) throws IOException, TemplateException {
Template template = cfg.getTemplate(templateFileName);
File outputDir = new File("src/main/java/" + BASE_PACKAGE.replace('.', '/') + "/" + templateFileName.replace(".", "/") + "/");
if (!outputDir.exists()) {
outputDir.mkdirs();
}
File outputFile = new File(outputDir, outputFileName);
Writer writer = new FileWriter(outputFile);
template.process(model, writer);
writer.close();
}
// 将下划线命名转换为驼峰命名
private static String toCamelCase(String str, boolean capitalizeFirstLetter) {
StringBuilder sb = new StringBuilder();
boolean capitalize = capitalizeFirstLetter;
for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i);
if (ch == '_') {
capitalize = true;
} else {
if (capitalize) {
sb.append(Character.toUpperCase(ch));
capitalize = false;
} else {
sb.append(ch);
}
}
}
return sb.toString();
}
}
上述示例代码中,CodeGenerator类根据数据库表结构自动生成Model、Mapper、Service、Controller等类的代码。在main方法中,首先创建FreeMarker配置对象,然后初始化数据源,获取数据库中所有的表名,循环处理每个表,依次生成Mapper接口文件、Model类文件、Service类文件和Controller类文件。在生成每个类文件时,使用FreeMarker模板引擎读取对应的模板文件,并根据模板中的变量生成代码文件。其中,模板文件中的变量包括包名、表名、类名、属性名、方法名等信息。生成的类文件存储在src/main/java目录下,按照包名和类名的层次结构进行组织。
实现后端接口需要使用一个Web框架,如SpringMVC、SpringBoot等。以下是一个可能的示例,使用SpringBoot框架和MyBatis持久化框架:
public class Blog {
private Long id;
private String title;
private String content;
private Date createTime;
private Date updateTime;
private List<Comment> comments;
// getter/setter省略
}
public class Comment {
private Long id;
private Long blogId;
private String content;
private Date createTime;
private Date updateTime;
// getter/setter省略
}
@Mapper
public interface BlogMapper {
Blog findById(Long id);
List<Blog> findAll();
List<Blog> findByTitle(String title);
void save(Blog blog);
void update(Blog blog);
void deleteById(Long id);
}
@Mapper
public interface CommentMapper {
Comment findById(Long id);
List<Comment> findByBlogId(Long blogId);
void save(Comment comment);
void update(Comment comment);
void deleteById(Long id);
}
public interface BlogService {
Blog findById(Long id);
List<Blog> findAll();
List<Blog> findByTitle(String title);
void save(Blog blog);
void update(Blog blog);
void deleteById(Long id);
}
public interface CommentService {
Comment findById(Long id);
List<Comment> findByBlogId(Long blogId);
void save(Comment comment);
void update(Comment comment);
void deleteById(Long id);
}
@Service
@Transactional
public class BlogServiceImpl implements BlogService {
@Autowired
private BlogMapper blogMapper;
@Autowired
private CommentMapper commentMapper;
@Override
public Blog findById(Long id) {
Blog blog = blogMapper.findById(id);
if (blog != null) {
List<Comment> comments = commentMapper.findByBlogId(id);
blog.setComments(comments);
}
return blog;
}
@Override
public List<Blog> findAll() {
return blogMapper.findAll();
}
@Override
public List<Blog> findByTitle(String title) {
return blogMapper.findByTitle(title);
}
@Override
public void save(Blog blog) {
blog.setCreateTime(new Date());
blog.setUpdateTime(new Date());
blogMapper.save(blog);
}
@Override
public void update(Blog blog) {
blog.setUpdateTime(new Date());
blogMapper.update(blog);
}
@Override
public void deleteById(Long id) {
commentMapper.deleteById(id);
blogMapper.deleteById(id);
}
}
@Service
@Transactional
public class CommentServiceImpl implements CommentService {
@Autowired
private CommentMapper commentMapper;
@Override
public Comment findById(Long id) {
return commentMapper.findById(id);
}
@Override
public List<Comment> findByBlogId(Long blogId) {
return commentMapper.findByBlogId(blogId);
}
@Override
public void save(Comment comment) {
comment.setCreateTime(new Date());
comment.setUpdateTime(new Date());
commentMapper.save(comment);
}
@Override
public void update(Comment comment) {
comment.setUpdateTime(new Date());
commentMapper.update(comment);
}
@Override
public void deleteById(Long id) {
commentMapper.deleteById(id);
}
}
@RestController
@RequestMapping("/api/blogs")
public class BlogController {
@Autowired
private BlogService blogService;
@GetMapping("/{id}")
public ResponseEntity<Blog> getBlogById(@PathVariable Long id) {
Blog blog = blogService.findById(id);
if (blog != null) {
return ResponseEntity.ok(blog);
} else {
return ResponseEntity.notFound().build();
}
}
@GetMapping
public ResponseEntity<List<Blog>> getAllBlogs() {
List<Blog> blogs = blogService.findAll();
return ResponseEntity.ok(blogs);
}
@PostMapping
public ResponseEntity<Void> createBlog(@RequestBody Blog blog) {
blogService.save(blog);
return ResponseEntity.created(URI.create("/api/blogs/" + blog.getId())).build();
}
@PutMapping("/{id}")
public ResponseEntity<Void> updateBlog(@PathVariable Long id, @RequestBody Blog blog) {
Blog existingBlog = blogService.findById(id);
if (existingBlog != null) {
blog.setId(id);
blogService.update(blog);
return ResponseEntity.noContent().build();
} else {
return ResponseEntity.notFound().build();
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteBlog(@PathVariable Long id) {
Blog existingBlog = blogService.findById(id);
if (existingBlog != null) {
blogService.deleteById(id);
return ResponseEntity.noContent().build();
} else {
return ResponseEntity.notFound().build();
}
}
}
@RestController
@RequestMapping("/api/comments")
public class CommentController {
@Autowired
private CommentService commentService;
@GetMapping("/{id}")
public ResponseEntity<Comment> getCommentById(@PathVariable Long id) {
Comment comment = commentService.findById(id);
if (comment != null) {
return ResponseEntity.ok(comment);
} else {
return ResponseEntity.notFound().build();
}
}
@GetMapping("/blog/{blogId}")
public ResponseEntity<List<Comment>> getCommentsByBlogId(@PathVariable Long blogId) {
List<Comment> comments = commentService.findByBlogId(blogId);
return ResponseEntity.ok(comments);
}
@PostMapping
public ResponseEntity<Void> createComment(@RequestBody Comment comment) {
commentService.save(comment);
return ResponseEntity.created(URI.create("/api/comments/" + comment.getId())).build();
}
@PutMapping("/{id}")
public ResponseEntity<Void> updateComment(@PathVariable Long id, @RequestBody Comment comment) {
Comment existingComment = commentService.findById(id);
if (existingComment != null) {
comment.setId(id);
commentService.update(comment);
return ResponseEntity.noContent().build();
} else {
return ResponseEntity.notFound().build();
}
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteComment(@PathVariable Long id) {
Comment existingComment = commentService.findById(id);
if (existingComment != null) {
commentService.deleteById(id);
return ResponseEntity.noContent().build();
} else {
return ResponseEntity.notFound().build();
}
}
}
可以使用Postman等工具测试上述接口的正确性和可用性。例如,可以发送以下HTTP请求:
GET /api/blogs/1
返回响应:
{
"id": 1,
"title": "My First Blog",
"content": "This is my first blog post.",
"createTime": "2021-01-01T00:00:00Z",
"updateTime": "2021-01-01T00:00:00Z",
"comments": [
{
"id": 1,
"blogId": 1,
"content": "Great post!",
"createTime": "2021-01-02T00:00:00Z",
"updateTime": "2021-01-02T00:00:00Z"
},
{
"id": 2,
"blogId": 1,
"content": "I disagree with your point.",
"createTime": "2021-01-03T00:00:00Z",
"updateTime": "2021-01-03T00:00:00Z"
}
]
}
使用Vue CLI搭建前端环境的步骤如下:
Vue CLI需要Node.js环境,可以从Node.js官网下载并安装对应操作系统的Node.js版本。
打开命令行工具(如Windows上的cmd或PowerShell、macOS上的终端),运行以下命令安装Vue CLI:
npm install -g @vue/cli
该命令会在全局范围内安装Vue CLI。
在命令行工具中进入要创建Vue项目的目录,运行以下命令:
vue create my-project
其中,my-project是项目的名称,可以根据实际情况进行修改。
在运行命令后,Vue CLI会提示选择要使用的特性,如Babel、TypeScript、CSS预处理器等。按照需要进行选择或保持默认设置即可。
在项目目录下,运行以下命令启动本地开发服务器:
npm run serve
该命令会启动本地的开发服务器,并在浏览器中打开网页。在开发过程中,可以在编辑器中修改代码并保存,浏览器会自动刷新并显示最新的页面。
以下是一个可能的示例,使用Vue框架和Axios库进行实现:
Axios是一个基于Promise的HTTP库,用于发送Ajax请求。在命令行工具中运行以下命令安装Axios:
npm install axios
在Vue项目的src/components目录下,定义以下组件:
BlogList.vue:用于显示博客列表。代码如下:
Blog List
-
{{ blog.title }}
{{ blog.content }}
{{ blog.createTime }}
BlogDetail.vue:用于显示单篇博客的详情和评论列表。代码如下:
{{ blog.title }}
{{ blog.content }}
{{ blog.createTime }}
Comments
-
{{ comment.content }}
{{ comment.createTime }}
TagList.vue:用于显示博客的分类标签。代码如下:
Tags
-
{{ tag }}
在Vue项目的src/router/index.js文件中,定义以下路由:
import Vue from 'vue';
import VueRouter from 'vue-router';
import BlogList from '@/components/BlogList.vue';
import BlogDetail from '@/components/BlogDetail.vue';
Vue.use(VueRouter);
const routes = [
{
path: '/',
redirect: '/blogs',
},
{
path: '/blogs',
name: 'BlogList',
component: BlogList,
},
{
path: '/blogs/:id',
name: 'BlogDetail',
component: BlogDetail,
},
];
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes,
});
export default router;
Blog List
Tags
在命令行工具中运行以下命令启动本地开发服务器:
npm run serve
在浏览器中打开http://localhost:8080 ↗,即可看到博客列表和分类标签。点击博客标题或分类标签,可以进入博客详情或显示对应标签的博客列表。
在前端页面中集成后端接口,可以通过axios等工具调用后端接口获取数据展示在页面上。以下是一个可能的示例:
在后端(例如Node.js)中,定义以下接口:
GET /api/blogs:获取博客列表
GET /api/blogs/:id:获取指定id的博客
GET /api/tags:获取博客分类标签列表
在接口中,可以返回JSON格式的数据,例如:
[
{
"id": 1,
"title": "My First Blog",
"content": "This is my first blog",
"createTime": "2022-01-01T00:00:00.000Z",
"tags": ["tag1", "tag2"],
"comments": [
{
"id": 1,
"content": "Great blog",
"createTime": "2022-01-02T00:00:00.000Z"
},
{
"id": 2,
"content": "Nice work",
"createTime": "2022-01-03T00:00:00.000Z"
}
]
},
{
"id": 2,
"title": "My Second Blog",
"content": "This is my second blog",
"createTime": "2022-01-04T00:00:00.000Z",
"tags": ["tag2", "tag3"],
"comments": [
{
"id": 3,
"content": "Keep going",
"createTime": "2022-01-05T00:00:00.000Z"
}
]
}
]
在Vue项目的src/components目录中,修改之前定义的组件,使用axios库调用后端接口获取数据。例如,在BlogList.vue组件中,可以修改mounted方法如下:
mounted() {
axios.get('/api/blogs').then((response) => {
this.blogs = response.data;
}).catch((error) => {
console.error(error);
});
}
在BlogDetail.vue组件中,可以修改mounted方法如下:
mounted() {
const id = this.$route.params.id;
axios.get(`/api/blogs/${id}`).then((response) => {
this.blog = response.data;
this.comments = this.blog.comments;
}).catch((error) => {
console.error(error);
});
}
在TagList.vue组件中,可以修改mounted方法如下:
mounted() {
axios.get('/api/tags').then((response) => {
this.tags = response.data;
}).catch((error) => {
console.error(error);
});
}
在命令行工具中运行以下命令启动本地开发服务器:
npm run serve
在浏览器中打开http://localhost:8080 ↗,即可看到博客列表和分类标签。点击博客标题或分类标签,可以进入博客详情或显示对应标签的博客列表。在后端接口返回的JSON数据中,可以包含博客内容、分类标签、评论等信息,前端页面可以根据需要进行展示。
将前端页面和后端接口部署到服务器上,可以使用Nginx等工具进行部署和代理。以下是一个可能的示例:
将后端接口部署到服务器上,可以使用PM2等工具进行管理和启动。例如,在服务器上创建一个名为myapp的目录,将后端代码上传到该目录下,然后使用以下命令安装PM2并启动应用:
npm install pm2 -g
pm2 start index.js --name myapp
其中,index.js是后端代码的入口文件,myapp是应用的名称。使用pm2 logs myapp命令可以查看应用的日志。
在本地使用npm run build命令可以将Vue项目打包成静态文件,然后将打包后的文件上传到服务器上。例如,在服务器上创建一个名为myapp的目录,将打包后的文件上传到该目录下。在Nginx配置文件中添加以下配置:
server {
listen 80;
server_name myapp.com;
root /path/to/myapp/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
其中,myapp.com是域名或服务器IP地址,/path/to/myapp是前端页面的根目录。在location / {}中,try_files指令用于尝试访问请求的文件或目录,如果不存在则返回index.html文件。
如果前端页面和后端接口部署在不同的服务器上,可以使用Nginx等工具进行反向代理。例如,在前端服务器上的Nginx配置文件中,可以添加以下配置:
server {
listen 80;
server_name myapp.com;
root /path/to/myapp/dist;
index index.html;
location /api/ {
proxy_pass http://backend-server/;
}
location / {
try_files $uri $uri/ /index.html;
}
}
其中,myapp.com是域名或服务器IP地址,/path/to/myapp是前端页面的根目录,backend-server是后端接口的地址。在location /api/ {}中,proxy_pass指令用于将请求转发到后端接口。在location / {}中,try_files指令用于尝试访问请求的文件或目录,如果不存在则返回index.html文件。
在修改Nginx配置文件或部署新的代码后,需要重启Nginx和PM2才能使更改生效。可以使用以下命令重启Nginx和PM2:
nginx -s reload
pm2 restart myapp
其中,myapp是应用的名称。使用pm2 logs myapp命令可以查看应用的日志。
实现个人博客需要前后端技术的综合运用,需要具备一定的数据库、后端、前端开发经验。