65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Spring Boot、AngularJS 和 MySQL 设计 UTF-8 编码的 Web 应用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.17/5 (3投票s)

2021 年 4 月 14 日

MIT

15分钟阅读

viewsIcon

30242

downloadIcon

122

创建一个支持 UTF-8 编码字符串的简单 Web 应用程序

引言

在我刚开始编程的时候,我曾尝试构建非英语的网站。那是 Geocity,全是静态页面,我不得不处理字符编码(多个字节代表一个字符),特别是中文,如 GB2312、GB18032 或 BIG5 字符编码。与此同时,国际委员会正致力于制定 UNICODE(16 位和 32 位)字符集。然后出现了 UTF-8 编码并成为标准,它是一种可变长度字符编码。这比字符编码更糟糕,因为不同的字符可以有不同的字节数来表示它们。如果有人必须编写这样的字符编码解码器,那将是一场噩梦。这种编码的优点在于,它是向字符集中添加更多字符的最灵活的方式。多年来,随着它成为标准,所有的操作系统都支持 UTF-8,浏览器也支持它,越来越多的网站和页面使用它,它是唯一支持显示这个星球上所有语言(再加上一些)的方式。

多年来,我没有处理过这些问题。我在美国工作,我只处理 ASCII。在我忙于自己的生活时,世界在前进。技术在发展,处理 UTF-8 编码变得非常容易。十五年前的难题现在可以轻易解决。我想如果我足够长地忽略这个问题,它就会消失。或者它已经被简化到可以用很少的麻烦来解决。

本教程的目的是记录设置一个 Spring Boot Web 应用程序所需的所有必要步骤,该应用程序可以正确处理 UTF-8 编码。如果我需要做类似的事情,我可以参考本教程。我相信在不久的将来,我会将此功能添加到我的新项目中。并且拥有这个可以找到的地方将非常有益。

一个快速的免责声明,我创建本教程及其示例应用程序时没有进行深入研究。所以我的知识是有限的。本教程附带的示例应用程序只是一个起点,它不是你需要知道或必须知道的一切。其中会有差距,并可能给你带来潜在的问题,请在出现问题时自行研究解决。我希望这不会吓到你,如果会,请继续阅读。

整体架构

我使用 MySQL 来存储数据。为了简单起见,只有一个表。数据库及其表设置为使用 UTF-8 字符集。示例应用程序采用 Spring Boot 设计,一个简单的控制器处理来自网页的请求。只有一个页面,AngularJS 应用程序处理与页面的用户交互。

要让所有这些工作,需要进行一些设置/配置:

  • 设置数据库和表以处理 UTF8。
  • 设置网页,以便它能够支持全页显示并使用 UTF8。
  • 在网页上,必须正确配置表单以处理 UTF-8 数据(用于请求和响应)。
  • 在服务器端,你需要确保 UTF8 字符串可以存储到数据库中并可以检索回来。

除了这些额外的步骤,Web 应用程序的配置与不支持 UTF8 的 Web 应用程序相同。在接下来的几节中,我将讨论如何完成这些设置。让我们先从数据库和表的创建开始。

MySQL 数据库和表

我做的第一件事是查找我需要为 MySQL 数据库和创建表所做的配置。有很多资源详细解释了如何做到这一点。关键是字符集必须设置为:utf8mb4。你不能使用 UTF-8 的原因是它不是标准 UTF-8 的相同字符集。MySQL 使用的这种 UTF-8 charset 是定制的字符编码。所以忘掉 UTF-8 编码,使用 utf8mb4。如果你需要更多信息,请在线查找。

这是我创建数据库、访问用户并配置数据库字符集的 SQL 脚本

DROP DATABASE IF EXISTS `utf8testdb`;

DROP USER IF EXISTS 'utf8tdbuser'@'localhost';

CREATE DATABASE `utf8testdb`;

CREATE USER 'utf8tdbuser'@'localhost' IDENTIFIED BY '888$Test$888';

GRANT ALL PRIVILEGES ON `utf8testdb`.* TO 'utf8tdbuser'@'localhost';

FLUSH PRIVILEGES;

ALTER DATABASE `utf8testdb` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

前两行用于在两者都存在时删除数据库和用户。第三行创建数据库,第四行创建用户并设置密码。第五行授予用户对新创建数据库的所有特权。第六行将刷新特权,以便用户可以使用它们。最后一行最重要,它修改数据库并将数据库使用的字符集设置为 utf8mb4,并使用 utf8mb4_unicode_ci 规则来定义 string 的字符序列和排序规则。

创建存储数据的表非常简单,如下所示

USE `utf8testdb`;

DROP TABLE IF EXISTS `testcontent`;

CREATE TABLE `testcontent` (
   `id` VARCHAR(34) NOT NULL PRIMARY KEY,
   `subject` VARCHAR(512) NOT NULL,
   `content` MEDIUMTEXT NOT NULL
);

由于整个数据库都设置为使用正确的 UTF-8 字符集,因此无需在表级别设置 UTF-8 特定配置。如果你不想将整个数据库配置为使用正确的 UTF-8 字符集,可以在表级别设置 UTF-8。我确信这是可能的。

一旦表创建完毕,为了测试它,我可以创建一个简单的 insert 语句并插入一些模拟数据,看看它是否有效。然后使用简单的查询检索它,看看值是否正确显示。这是测试 insert 和查询语句的截图

接下来,我将讨论如何创建支持 UTF8 的 HTML 页面。

用于 UTF-8 数据处理的 HTML 页面

为了演示,Web 应用程序很简单。它有一个表单,允许用户输入消息主题和消息内容,然后点击一个按钮。主题和内容将被发送到后端进行存储。一旦存储完成,服务器端将执行查询并获取所有已存储消息的列表。为了支持 UTF-8 字符处理,我必须对网页做两件事。一是声明页面使用 UTF-8 字符集进行显示;二是设置用于数据交换的 charset 为 UTF8。这些都很简单。这是 Web 应用程序整个页面的源代码

<!DOCTYPE html>
<html>
   <head>
      <title>AngularJS HTML Editor Directive - by Han</title>
      <meta http-equiv="Content-Type" content="text/html; 
      charset=utf-8">
      <link href="/assets/bootstrap/css/bootstrap.min.css" rel="stylesheet">
      <link href="/assets/styles/index.css" rel="stylesheet">    
   </head>
   <body>
      <div class="container" ng-app="testSampleApp" 
      ng-controller="testSampleController as vm">

         <div class="row">
            <div class="col-xs-12">
               <h3>UTF8 Web App</h3>
            </div>
         </div>
         <div class="row">
            <div class="col-xs-12">
               <form accept-charset="utf-8">
                  <div class="form-group">
                     <label for="subjectLine">Subject</label>
                     <input id="subjectLine" class="form-control" 
                     maxlength="100" ng-model="vm.subjectLine">
                  </div>
                  <div class="form-group">
                     <label for="subjectContent">Content</label>
                     <textarea id="subjectContent"
                               class="form-control fixed-area"
                               maxlength="32767"
                               ng-model="vm.subjectContent"
                               rows="5"></textarea>
                  </div>
               </form>
            </div>
            <div class="col-xs-6">
               <button class="btn btn-success" 
               ng-click="vm.saveUtf8Content()">Save</button>
               <button class="btn btn-default" 
               ng-click="vm.clearContent()">Clear</button>
            </div>
         </div>
         
         <div ng-if="vm.postsList && vm.postsList.length > 0">
	         <hr>
	         <div class="row" ng-repeat="post in vm.postsList">
	            <div class="col-xs-12">
	               <span>{{post.postId}}</span><br>
	               <span>{{post.title}}</span><br>
                  <div ng-bind-html="post.safeHtmlContent"></div>
                  <hr>
	            </div>
	         </div>
         </div>
         
         <footer class="footer">
            <p>© 2021, Han Bo Sun.</p>
         </footer>

      </div>
    
      <script type="text/javascript" 
      src="/assets/jquery/js/jquery.min.js"></script>
      <script type="text/javascript" 
      src="/assets/bootstrap/js/bootstrap.min.js"></script>
      <script type="text/javascript" 
      src="/assets/angularjs/1.7.5/angular.min.js"></script>
      <script type="text/javascript" 
      src="/assets/angularjs/1.7.5/angular-resource.min.js"></script>
      <script type="text/javascript" 
      src="/assets/app/js/app.js"></script>
      <script type="text/javascript" 
      src="/assets/app/js/testSampleService.js"></script>
    </body>  
</html>

要声明此网页在显示时使用 UTF-8,我必须在页面的 head 部分添加一个 meta 标签,如下所示

<html>
   <head>
      ...
      <meta http-equiv="Content-Type" 
      content="text/html; charset=utf-8">
      ...
   </head>
   ...
</html>

这是我定义用于处理用户输入并将用户数据发送到后端的表单的部分,我已突出显示表单的开头,我必须在此添加 "accept-charset" 属性。这将强制将 accept-charset 添加到将用户数据发送到后端的请求的头中。这将确保字符字符串在请求中得到正确表示。由于数据是通过 ngResource 发送的,我不确定这是否必要。但为了以防万一,我还是把它放进了表单里。

<div class="row">
   <div class="col-xs-12">
      <form accept-charset="utf-8">
         <div class="form-group">
            <label for="subjectLine">Subject</label>
            <input id="subjectLine" class="form-control" 
             maxlength="100" ng-model="vm.subjectLine">
         </div>
         <div class="form-group">
            <label for="subjectContent">Content</label>
            <textarea id="subjectContent"
                        class="form-control fixed-area"
                        maxlength="32767"
                        ng-model="vm.subjectContent"
                        rows="5"></textarea>
         </div>
      </form>
   </div>
   <div class="col-xs-6">
      <button class="btn btn-success" 
      ng-click="vm.saveUtf8Content()">Save</button>
      <button class="btn btn-default" 
      ng-click="vm.clearContent()">Clear</button>
   </div>
</div>

如上所示,"subjectLine" 输入字段绑定到作用域变量 vm.subjectLine,而 "subjectContent" 文本区域绑定到作用域变量 vm.subjectContent。没有什么特别之处。将用户请求发送到后端服务器的按钮定义如下

 <button class="btn btn-success" 
 ng-click="vm.saveUtf8Content()">Save</button>

这里也是显示数据库中所有已存储消息的地方,我只是使用 ngRepeat 将它们逐一列出,如下所示

<div ng-if="vm.postsList && 
vm.postsList.length > 0">
   <hr>
   <div class="row" 
   ng-repeat="post in vm.postsList">
      <div class="col-xs-12">
         <span>{{post.postId}}</span><br>
         <span>{{post.title}}</span><br>
         <div ng-bind-html="post.safeHtmlContent"></div>
         <hr>
      </div>
   </div>
</div>

总之,这就是 HTML 代码的全部内容。接下来,我将讨论处理数据交换的 AngularJS 代码。

AngularJS 代码

这个 Web 应用程序前端最核心的部分是 AngularJS 代码。我将其分为两个文件。一个名为 app.js,另一个名为 testSampleService.js。第一个文件是前端应用程序的主入口。另一个是发送和接收与服务器数据的服务对象。

这是 app.js 文件的源代码

(function () {
   "use strict";
   var mod = angular.module("testSampleApp", 
   [ "testSampleServiceModule" ]);
   mod.controller("testSampleController", 
   [ "$scope", "$sce", 
   "testSampleService", function ($scope, $sce, testSampleService) {
      var vm = this;
      
      vm.subjectLine = null;
      vm.subjectContent = null;
      
      vm.postsList = null;
      
      loadAllPosts();
      
      vm.saveUtf8Content = function () {
         var obj = {
            title: vm.subjectLine,
            content: vm.subjectContent
         };
         
         testSampleService.sendPost(obj).then(function (result) {
            if (result && result.status === true) {
               loadAllPosts();
            }
         }, function (error) {
            if (error) {
               console.log(error);
            }
         });
      };
      
      vm.clearContent = function () {
         vm.subjectLine = null;
         vm.subjectContent = null;
      };
      
      function loadAllPosts() {
         testSampleService.listAllPosts().then(function(results) {
            if (results && results.length > 0) {
               vm.postsList = results;
               
               angular.forEach(vm.postsList, function (post) {
                  if (post) {
                     post.safeHtmlContent = $sce.trustAsHtml(post.content);
                  }
               });
            };
         }, function (error) {
            if (error) {
               console.log(error);
            }
            vm.postsList = null;
         });
      }
   }]);
})();

如所示,我无需做任何特别的事情来支持基于 UTF-8 的数据交换。有一点是肯定的,AngularJS 是一个现代框架,无需过多配置即可支持字符编码。我相信所有其他框架也能很好地支持这一点。

在上面的代码中,加载所有主题和内容的函数称为 loadAllPosts()。这里是

function loadAllPosts() {
   testSampleService.listAllPosts().then(function(results) {
      if (results && results.length > 0) {
         vm.postsList = results;
         
         angular.forEach(vm.postsList, function (post) {
            if (post) {
               post.safeHtmlContent = $sce.trustAsHtml(post.content);
            }
         });
      };
   }, function (error) {
      if (error) {
         console.log(error);
      }
      vm.postsList = null;
   });
}

在函数中,我调用服务 testSampleService 的方法 listAllPosts() 从后端获取所有消息内容对象。然后我必须将对象的内容转换为安全的/可显示的 HTML 字符串。这是调用 $sce.trustAsHtml()

app.js 的另一个重要部分是保存消息对象到后端的 `vm.saveUtf8Content()` 方法。

vm.saveUtf8Content = function () {
   var obj = {
      title: vm.subjectLine,
      content: vm.subjectContent
   };
   
   testSampleService.sendPost(obj).then(function (result) {
      if (result && result.status === true) {
         loadAllPosts();
      }
   }, function (error) {
      if (error) {
         console.log(error);
      }
   });
};

服务 testSampleService.js 是为了处理与后端的 A 数据交换而创建的。这是整个文件

(function () {
   "use strict";
   var mod = angular.module("testSampleServiceModule", 
   [ "ngResource" ]);
   mod.factory("testSampleService", [ "$resource",
      function ($resource) {
         var svc = {
            sendPost: sendPost,
            listAllPosts: listAllPosts
         };
         
         var apiRes = $resource(null, null, {
            sendPost: {
               url: "/processPost",
               method: "post",
               isArray: false
            },
            listAllPosts: {
               url: "/listAllPosts",
               method: "get",
               isArray: true
            }
         });
         
         function sendPost(postInfo) {
            return apiRes.sendPost(postInfo).$promise;
         }
         
         function listAllPosts() {
            return apiRes.listAllPosts().$promise;
         }
         
         return svc;
      }
   ]);
})();

同样,处理数据交换也无需做任何特别的事情。后端服务是 RESTful 服务,因此 ngResource 可以工作。如果我需要任何额外的头信息,我可以将其添加到 API 调用中。但我没有。

接下来,我将讨论后端服务的设计。这是我不太了解的部分。如果你不知道,我是在 90 年代从 C/C++ 开始的。那时,字符用字节表示,然后有了双字节的 UNICODE 字符。然后是称为字符编码的多字节字符代码,这也是双字节。然后是 4 字节的超级 UNICODE。然后 UTF-8 作为可变长度出现,一个字符可以是 1 字节、2 字节或 4 字节。Java 和 C# 都将 UNICODE 用作字符。这个应用程序使用 Java,所以我有点不确定 UTF-8 字符是如何存储的。在这里我承认我对编码的了解不够充分。问题是我是否需要使用任何编码转换来在 UNICODE 和 UTF-8 之间进行转换。

后端 RESTFul 服务 API

结果是,我也无需做任何特别的事情。当包含 UTF-8 字符 string 的请求传递到后端服务时,string 将表示为 UTF-8 并存储在 Java String 对象中。它不是双字节 UNICODE string 或其他编码。这当然非常方便。总之,在我深入探讨后端服务之前,我将从头开始。这些几乎都是我过去所做内容的回顾。所以没有新的惊喜。

让我从后端应用程序的入口开始。它看起来像这样

package org.hanbo.boot.rest;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App
{   
   public static void main(String[] args)
   {
      SpringApplication.run(App.class, args);
   }
}

这个启动入口是我从过去三年中无数教程中的一个复制粘贴过来的。我已经解释过它的工作原理,这里不再赘述。

接下来,我需要一个可以提供 MySQL 数据库连接的配置类。这同样是从我过去的一个教程中复制粘贴的。我使用 JdbcTemplate 和普通的 SQL 查询和更新来完成这个项目。这是这个配置对象的完整源代码

package org.hanbo.boot.rest;

import javax.sql.DataSource;

import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

@Configuration
public class DataAccessConfiguration
{
   @Value("${db.jdbc.driver}")
   private String dbJdbcDriver;
   
   @Value("${db.conn.string}")
   private String dbConnString;
   
   @Value("${db.access.username}")
   private String dbAccessUserName;

   @Value("${db.access.password}")
   private String dbAccessPassword;

   @Value("${db.access.validity.query}")
   private String dbAccessValityQuery;
   
   @Bean
   public DataSource dataSource()
   {
      BasicDataSource dataSource = new BasicDataSource();

      dataSource.setDriverClassName(dbJdbcDriver);
      dataSource.setUrl(dbConnString);
      dataSource.setUsername(dbAccessUserName);
      dataSource.setPassword(dbAccessPassword);
      dataSource.setMaxIdle(4);
      dataSource.setMaxTotal(20);
      dataSource.setInitialSize(4);
      dataSource.setMaxWaitMillis(900000);
      dataSource.setTestOnBorrow(true);
      dataSource.setValidationQuery(dbAccessValityQuery);
      
      return dataSource;
   }
   
   @Bean
   public NamedParameterJdbcTemplate namedParameterJdbcTemplate()
   {
      NamedParameterJdbcTemplate retVal
          = new NamedParameterJdbcTemplate(dataSource());
       return retVal;
   }

   @Bean
   public DataSourceTransactionManager txnManager()
   {
      DataSourceTransactionManager txnManager
         = new DataSourceTransactionManager(dataSource());
      return txnManager;
   }
}

这比入口类稍微复杂一些。我将重复一遍,以免你必须查找我之前的教程。首先,我需要从名为 application.properties 的应用程序配置文件加载一些数据。这是我这样做的

@Value("${db.jdbc.driver}")
private String dbJdbcDriver;

@Value("${db.conn.string}")
private String dbConnString;

@Value("${db.access.username}")
private String dbAccessUserName;

@Value("${db.access.password}")
private String dbAccessPassword;

@Value("${db.access.validity.query}")
private String dbAccessValityQuery;

这些行按键加载配置属性。例如,这一行

@Value("${db.jdbc.driver}")
private String dbJdbcDriver;

它将从 application.properties 文件中读取这一行

db.jdbc.driver=com.mysql.cj.jdbc.Driver
...
...

接下来,我需要定义一个 Java bean,它应该命名为 dataSource。这个 Java bean 将指定应用程序的 MySQL 连接信息。

@Bean
public DataSource dataSource()
{
   BasicDataSource dataSource = new BasicDataSource();

   dataSource.setDriverClassName(dbJdbcDriver);
   dataSource.setUrl(dbConnString);
   dataSource.setUsername(dbAccessUserName);
   dataSource.setPassword(dbAccessPassword);
   dataSource.setMaxIdle(4);
   dataSource.setMaxTotal(20);
   dataSource.setInitialSize(4);
   dataSource.setMaxWaitMillis(900000);
   dataSource.setTestOnBorrow(true);
   dataSource.setValidationQuery(dbAccessValityQuery);
   
   return dataSource;
}

接下来,我需要一个 bean,它将始终给我一个 JdbcTemplate 对象,它将使用前面定义的 dataSource bean

@Bean
public NamedParameterJdbcTemplate namedParameterJdbcTemplate()
{
   NamedParameterJdbcTemplate retVal
      = new NamedParameterJdbcTemplate(dataSource());
   return retVal;
}

最后,我需要一个事务管理器。我需要它是因为我可以使用 @Transactional 注解来修饰方法。这允许我根据单个方法中 DB 操作的成功与否来提交或回滚更改。这是我定义这个事务管理器的方式

@Bean
public DataSourceTransactionManager txnManager()
{
   DataSourceTransactionManager txnManager
      = new DataSourceTransactionManager(dataSource());
   return txnManager;
}

一旦有了应用程序入口和这个 DataAccessConfiguration 类,我就可以创建 API 控制器和服务类。Service 类是使用 JdbcTemplate 执行 CRUD 操作的类。API 控制器将处理来自用户的 HTTP 请求。

这是 RESTFul API 控制器的完整源代码

package org.hanbo.boot.rest.controllers;

import java.util.ArrayList;
import java.util.List;

import org.hanbo.boot.rest.models.PostDataModel;
import org.hanbo.boot.rest.models.StatusModel;
import org.hanbo.boot.rest.services.PostDataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SampleFormController
{
   @Autowired
   private PostDataService postDataSvc;
   
   @RequestMapping(value="/processPost",
                   method=RequestMethod.POST,
                   consumes = "application/json",
                   produces = "application/json")
   public ResponseEntity<StatusModel> 
   processPost(@RequestBody PostDataModel postContent)
   {
      StatusModel resp = new StatusModel();
      if (postContent != null)
      {
         System.out.println
         (String.format("Title: [%s]", postContent.getTitle()));
         System.out.println
         (String.format("Content: [%s]", postContent.getContent()));
         
         String postId = postDataSvc.savePostData(postContent);
         if (postId != null && !postId.isEmpty())
         {
            resp.setStatus(true);
            resp.setMessage("Post has been processed successfully. postID: " + postId);
         }
         else
         {
            resp.setStatus(true);
            resp.setMessage("Unable to save post entity. Unknown error.");
         }
      }
      else
      {
         resp.setStatus(true);
         resp.setMessage("Unable to save post entity. No valid post object available.");
      }
      
      ResponseEntity<StatusModel> retVal = ResponseEntity.ok(resp);
      
      return retVal;
   }
   
   @RequestMapping(value="/listAllPosts",
                   method=RequestMethod.GET,
                   produces = "application/json")
   public ResponseEntity<List<PostDataModel>> getAllPosts()
   {
      List<PostDataModel> retList = postDataSvc.getAllPostData();
      if (retList == null)
      {
         retList = new ArrayList<PostDataModel>();
      }
      
      ResponseEntity<List<PostDataModel>> 
      retVal = ResponseEntity.ok(retList);
      
      return retVal;
   }
}

将对象保存到数据库的方法名为 processPost。映射到此方法的 URL 是 "/processPost"。在此方法中,当我收到请求对象时,我将使用 System.out.println() 将请求的主题和内容属性打印到控制台。这是数据输出到控制台时的截图

控制器将繁重的工作交给了名为 postDataSvc 的服务对象。它的类型是 PostDataService。这是这个 PostDataService 的完整源代码

package org.hanbo.boot.rest.services;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.hanbo.boot.rest.models.PostDataModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class PostDataServiceImpl
   implements PostDataService
{
   private final String sql_insertPostData = 
   "INSERT INTO `testcontent` (id, subject, content) 
   VALUES (:id, :title, :content);";

   private final String sql_queryAllPosts = 
   "SELECT id, subject, content FROM `testcontent` LIMIT 1000;";
   
   @Autowired
   private NamedParameterJdbcTemplate sqlDao;
   
   @Override
   @Transactional
   public String savePostData(PostDataModel dataToSave)
   {
      if (dataToSave != null)
      {
         String title = dataToSave.getTitle();
         if (title == null || title.isEmpty())
         {
            throw new RuntimeException("Title is NULL or empty");
         }
         
         String content = dataToSave.getContent();
         if (content == null || content.isEmpty())
         {
            throw new RuntimeException("Content is NULL or empty");
         }
         
         Map<String, Object> parameters = new HashMap<String, Object>();
         
         String postId = generateId();
         parameters.put("id", postId);
         parameters.put("title", dataToSave.getTitle());
         parameters.put("content", dataToSave.getContent());
         
         int updateCount = sqlDao.update(sql_insertPostData, parameters);
         if (updateCount > 0)
         {
            return postId;
         }         
      }
      
      return "";
   }

   @Override
   public List<PostDataModel> getAllPostData()
   {
      List<PostDataModel> retVal = new ArrayList<PostDataModel>();
      
      retVal = sqlDao.query(sql_queryAllPosts,
            (MapSqlParameterSource)null,
            (rs) -> {
               List<PostDataModel> foundObjs = new ArrayList<PostDataModel>();
               if (rs != null)
               {
                  while (rs.next())
                  {
                     PostDataModel postToAdd = new PostDataModel();
                     postToAdd.setPostId(rs.getString("id"));
                     postToAdd.setTitle(rs.getString("subject"));
                     postToAdd.setContent(rs.getString("content"));
                     
                     foundObjs.add(postToAdd);
                  }
               }
               
               return foundObjs;
            });

      return retVal;
   }

   private static String generateId()
   {
      UUID uuid = UUID.randomUUID();
      String retVal = uuid.toString().replaceAll("-", "");
      
      return retVal;
   }
}

我将服务分为了接口和实现类。上面的代码是实现类。在其中,我使用了 JdbcTemplate 来处理数据访问代码。如果你回顾上面的代码,同样没有什么特别之处。如果你有兴趣,这是将记录插入数据库的代码

Map<String, Object> parameters = new HashMap<String, Object>();
         
String postId = generateId();
parameters.put("id", postId);
parameters.put("title", dataToSave.getTitle());
parameters.put("content", dataToSave.getContent());

int updateCount = sqlDao.update(sql_insertPostData, parameters);
if (updateCount > 0)
{
   return postId;
}

这是从表中查询所有已保存行的代码

retVal = sqlDao.query(sql_queryAllPosts,
      (MapSqlParameterSource)null,
      (rs) -> {
         List<PostDataModel> foundObjs = new ArrayList<PostDataModel>();
         if (rs != null)
         {
            while (rs.next())
            {
               PostDataModel postToAdd = new PostDataModel();
               postToAdd.setPostId(rs.getString("id"));
               postToAdd.setTitle(rs.getString("subject"));
               postToAdd.setContent(rs.getString("content"));
               
               foundObjs.add(postToAdd);
            }
         }
         
         return foundObjs;
      });

这里的技巧是,当请求数据进来时,它以正确的字符集编码存储到数据库中。并且数据库已正确配置数据,因此当检索行时,数据与原始数据相同。因此,当检索数据时,它们可以按原始请求的方式返回。

POM 文件

我最后想提的是这个项目的 Maven POM 文件。这个文件里只有一个属性我想指出来,它指定了源文件的编码为 UTF-8。如果源文件或静态文件使用 UTF-8 字符集,这将非常有用。它还消除了构建警告:"[WARNING] Using platform encoding (CP1251 actually) to copy filtered resources, i.e. build is platform dependent!"

我就是这样指定配置的

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
    http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    ...
    
    <properties>
      ...
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    
    ...
</project>

这就是这个示例应用程序的全部内容。现在所有内部细节都已讨论完毕,是时候进行测试了。

如何运行示例应用程序

下载 zip 格式的源代码后,首先应该将所有 *.sj 文件重命名为 *.js 文件。

然后使用终端(或命令提示符)转到解压后的源代码的根目录。然后输入以下命令

mvn clean install

项目构建成功后,在同一个终端/命令提示符中,运行以下命令

java -jar target/hanbo-agular-utf8webapp-1.0.1.jar

命令成功运行后,使用浏览器导航到

http://locahost:8080/

当网页显示出来时,你可以在主题行和内容文本区域输入一些英文、中文、日文或韩文。你也可以尝试一些其他语言,如希伯来语、阿拉伯语或印地语。然后点击名为 "Save" 的绿色按钮。保存的请求数据将显示在底部

如所示,我尝试了中文、日文和韩文。所有这些在保存到数据库后都得到了正确检索和显示。当我在 MySQL Workbench 中运行查询时,我可以在数据库表中看到这些

两者(页面显示和数据库查询)都应验证端到端的应用程序执行是正确的。但是,正如我在免责声明中所述,我不是 Web 应用程序中使用 UTF-8 字符集的专家,所以可能有些事情我没做到,应用程序可能会因此崩溃。我希望不会发生这种情况。总之,请按原样使用示例项目。

摘要

编写一个以 UTF-8 为基础字符集的基于 Web 的应用程序是我一直想做的事情。但直到现在已经过了这么多年,我才迈出了第一步。我创建本教程是为了将来需要做类似的事情时可以参考。我以为会很难。结果发现一点也不难。我想以我的经验和技能水平,没有什么 Web 应用程序设计对我来说会是难事了。

最大的挑战将是数据库配置。MySQL 的诀窍是使用 utf8mb4 编码而不是 UTF-8 编码。接下来,我必须确保网页使用 UTF-8 作为字符编码,并且表单使用 UTF-8 作为字符集。Java 端没有任何变化,因为 Java 字符串对象可以存储 UTF-8,而且不需要字符串转换。只要字符串数据被正确编码(UTF-8),那么端到端的测试运行使用示例应用程序就应该能正确工作。

创建这个示例应用程序非常有趣。当然,我学到了有价值的东西。我希望它对各位读者有所帮助。如果你发现任何奇怪的地方,请在本教程的评论区写下来。这对他人和我自己都有帮助。感谢您的阅读。

历史

  • 2021 年 4 月 12 日 - 初稿
© . All rights reserved.