基于SSM框架搭建的仿知乎问答社区
一.项目的来源
项目来源自国内知名刷题网站的项目,主要是给在校的学生一个更加真实的实战项目,非经验,有些东西不是自己独立在实践生产中写的东西只能说是一种经历,但是比起书本上的项目的话确实挺值的.
二.项目环境搭建与编写
2.1开发环境
- 操作系统 : Ubuntu 17.04
- IDE :IntelliJ IDEA 2017.1.3 x64 && Pycharm 2017.1.1
- JDK Version : JDK1.8 建议使用JDK1.7以上版本
- Python Version : Python 2.7
- Web容器 : SpringBoot集成的默认容器-Tomcat
- 数据库 :Mysql-5.7
- 依赖管理工具 : Maven 管理jar包真的很方便
- 版本控制 : Git
2.2项目编写
- 在IDEA中生成一个SpringBoot工程还是挺方便的,填入跟选择相对应的值直接一步步就可以了
还有一种方法就是在Spring官网生成好一个工程再导入IDEA中:
2.2.1登录跟注册
-
引入了
ticket
的概念,在用户登录之后会后台自动产生一个ticket,然后放入数据库中,ticket
具有时效性的,通过保存的过期时间跟当前服务器时间进行对比,如果过期了就需要重新登录,如果没有过期就会直接是登录状态,这里的ticket
在服务器发送给客户端后保存在Cookie
中 -
在进行登录注册的时候保存当前用户访问的网址,在登录或者注册完成后直接跳回操作的网址,提升用户的体验性
2.2.2敏感词过滤
正如大多数网站发表言论都是需要经过审查的,而在我们的局域网里面,更是是要经过无数次的审查,然后方案有一下几种:
- 把敏感词删除
- 把敏感词替换为指定的字符串
这里并不是使用
replace()
方法或者使用正则表达式进行逐个查找然后进行替换,而是采用更加优良的算法-
前缀树
2.2.3前缀树
- 根节点不包含字符,除根节点外每一个节点都只包含一个字符
- 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串
- 每个节点的所有子节点包含的字符都不相同
2.2.4异步
在当今互联网这种上网人群多,访问量大的情况下,异步编程提供了一个非堵塞的,基于事件驱动的编程模型.这种编程方式可以充分利用计算机的多核来同时执行并行任务,提高资源的利用效率.应用场景在我了解的也不多,可以想象应用场景是很广泛的.举个很简单的例子,我们在网站注册新的社交账号的话,严格一点的网站都是会有验证邮箱地址的邮件.这个就是一个异步的事件,你不可能说直接把邮件验证的流程嵌套在业务逻辑里面.应该是把这个事件发送给一个队列里面,然后队列专门处理这种事件.在一个网站中有各种各样的事情需要进行处理
简单设计流程:
-
1.首先定义一个定义一个枚举类型的
EventType
类,这里面列举各种各样将会出现的事件 -
2.再定义一个事件模型
EventModel
类,里面包含的是一个事件所应该具有的一些属性.例如事件类型,操作者的ID,操作的实体类型,操作的实体ID,操作的实体拥有者等等 -
3.再定义一个接口
EventHandler
,里面写上几个抽象的方法,具体的实现由继承的类进行实现 -
4.定义一个事件生产者
EventProducer
,这里就是用来生产各种各样的事件,如异常登录或者是注册邮件等等.本项目中这里就是把事件送到Redis中进行储存 -
5.定义一个事件消费者
EventConsumer
,这里就是需要继承InitializingBean
跟ApplicationContextAware
.继承InitializingBean
是可以定义bean的初始化方式,继承InitializingBean
是为了可以通过这个上下文对象得到我们想获取的bean.然后在这个类里面使用多线程一直去Redis里面读取出事件,然后进行处理 -
6.再继承
EventHandler
在方法里面写自己的实现
按照步骤来的代码示例:
-
EventType
类
java
public enum EventType { LIKE(0), COMMENT(1), LOGIN(2), MAIL(3), FOLLOW(4), UNFOLLOW(5); private int value; EventType(int value) { this.value = value; } public int getValue() { return value; }}
-
EventModel
类
````java public class EventModel {
private EventType type;
private int actorId;
private int entityType;
private int entityId;
private int entityOwnerId;
private Map<String, String> exts = new HashMap<>();
public EventModel() {
}
public EventModel setExt(String key, String value) {
exts.put(key, value);
return this;
}
public EventModel(EventType type) {
this.type = type;
}
public String getExt(String key) {
return exts.get(key);
}
public EventType getType() {
return type;
}
public EventModel setType(EventType type) {
this.type = type;
return this;
}
public int getActorId() {
return actorId;
}
public EventModel setActorId(int actorId) {
this.actorId = actorId;
return this;
}
public int getEntityType() {
return entityType;
}
public EventModel setEntityType(int entityType) {
this.entityType = entityType;
return this;
}
public int getEntityId() {
return entityId;
}
public EventModel setEntityId(int entityId) {
this.entityId = entityId;
return this;
}
public int getEntityOwnerId() {
return entityOwnerId;
}
public EventModel setEntityOwnerId(int entityOwnerId) {
this.entityOwnerId = entityOwnerId;
return this;
}
public Map<String, String> getExts() {
return exts;
}
public EventModel setExts(Map<String, String> exts) {
this.exts = exts;
return this;
}
}
````
-
EventHandler
接口
````java public interface EventHandler {
void doHandle(EventModel model);
List<EventType> getSupportEventTypes();
}
````
-
EventProducer
类
````java public class EventProducer {
private final JedisAdapter jedisAdapter;
@Autowired
public EventProducer(JedisAdapter jedisAdapter) {
this.jedisAdapter = jedisAdapter;
}
public boolean fireEvent(EventModel eventModel) {
try {
String json = JSONObject.toJSONString(eventModel);
String key = RedisKeyUtil.getEventQueueKey();
jedisAdapter.lpush(key, json);
return true;
} catch (Exception e) {
return false;
}
}
} ````
-
EventConsumer
类
````java public class EventConsumer implements InitializingBean, ApplicationContextAware {
private static Logger logger = LoggerFactory.getLogger(EventConsumer.class);
private Map<EventType, List<EventHandler>> config = new HashMap<>();
private ApplicationContext applicationContext;
private final JedisAdapter jedisAdapter;
@Autowired
public EventConsumer(JedisAdapter jedisAdapter) {
this.jedisAdapter = jedisAdapter;
}
@Override
public void afterPropertiesSet() throws Exception {
Map<String, EventHandler> beans = applicationContext.getBeansOfType(EventHandler.class);
if (beans != null) {
for (Map.Entry<String, EventHandler> entry : beans.entrySet()) {
List<EventType> eventTypes = entry.getValue().getSupportEventTypes();
for (EventType type : eventTypes) {
if (!config.containsKey(type)) {
config.put(type, new ArrayList<>());
}
config.get(type).add(entry.getValue());
}
}
}
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
String key = RedisKeyUtil.getEventQueueKey();
List<String> events = jedisAdapter.brpop(0, key);
for (String message : events) {
if (message.equals(key)) {
continue;
}
EventModel eventModel = JSON.parseObject(message, EventModel.class);
if (!config.containsKey(eventModel.getType())) {
logger.error("不能识别的事件");
continue;
}
for (EventHandler handler : config.get(eventModel.getType())) {
handler.doHandle(eventModel);
}
}
}
}
});
thread.start();
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
````
-
*Handler
继承类
````java public class LikeHandler implements EventHandler {
private final MessageService messageService;
private final UserService userService;
@Autowired
public LikeHandler(MessageService messageService, UserService userService) {
this.messageService = messageService;
this.userService = userService;
}
@Override
public void doHandle(EventModel model) {
Message message = new Message();
message.setFromId(WendaUtil.SYSTEM_USERID);
message.setToId(model.getEntityOwnerId());
message.setCreateDate(new Date());
User user = userService.getUser(model.getActorId());
message.setContent("用户" + user.getName() + "赞了你的评论,http://127.0.0.1:8080/question/" + model.getExt("questionId"));
// 这里的会话ID肯定是系统管理员跟通知用户之间的会话
message.setConversationId(WendaUtil.SYSTEM_USERID + "_" + model.getEntityOwnerId());
messageService.addMessage(message);
}
@Override
public List<EventType> getSupportEventTypes() {
return Arrays.asList(EventType.LIKE);
}
}
````
### 2.3Spring自定义bean的初始化方法
在写事件处理的时候继承了
Spring
的
InitializingBean
接口,这个接口只有一个方法:
````java public interface InitializingBean {
/**
* Invoked by a BeanFactory after it has set all bean properties supplied
* (and satisfied BeanFactoryAware and ApplicationContextAware).
* <p>This method allows the bean instance to perform initialization only
* possible when all bean properties have been set and to throw an
* exception in the event of misconfiguration.
* @throws Exception in the event of misconfiguration (such
* as failure to set an essential property) or if initialization fails.
*/
void afterPropertiesSet() throws Exception;
} ````
我们要对某个
bean
进行自定义的初始化的时候,我们就可以让bean继承这个接口,然后在里面写上我们的业务逻辑,在
Spring
初始化
bean
的时候就会检查
bean
是否继承了
InitializingBean
接口,然后再执行
afterPropertiesSet()
方法.这样使用虽然很方便,但是这个
bean
中的代码跟
Spring
耦合在一起了
2.4邮件
邮件发送倒也是第一次接触,好像以前看张孝祥老师的是有个邮件发送的视频的,不过当时也不会写.这里还是引用的是官方的
javax.mail
包,然后是通过
Spring
进行调用,这样可以减少一些邮件发送的复杂度,毕竟很多时候这种固定套路的代码都是复制过来用的.
流程: - 定义一个发送邮件的配置文件,里面写好相关的配置,包括服务器信息,邮件协议,账号信息等等
- 定义一个bean实现`InitializingBean`,然后也是在`afterPropertiesSet()`方法中初始化一些属性,这里就是在方法里面读取配置文件初始化`JavaMailSenderImpl`的几个必要的属性,方便我们后来的使用
-
在bean中写一个方法
sendWithHTMLTemplate()
用来发送邮件的,因为不可能每一个邮件都是一样的,这里是采用了Velocity
进行生成邮件文本的 -
在需要调用发送邮件的地方调用
sendWithHTMLTemplate()
发送邮件
示例代码:
+
MailSender
类
````java public class MailSender implements InitializingBean {
private static Logger logger = LoggerFactory.getLogger(MailSender.class);
private JavaMailSenderImpl mailSender;
private final VelocityEngine velocityEngine;
private final MailSetting mailSetting;
@Autowired
public MailSender(VelocityEngine velocityEngine, MailSetting mailSetting) {
this.velocityEngine = velocityEngine;
this.mailSetting = mailSetting;
}
public boolean sendWithHTMLTemplate(String to, String subject,
String template, Map<String, Object> model) {
try {
String nick = MimeUtility.encodeText("牛客中级课程");
InternetAddress from = new InternetAddress(nick + "<demo@vip.qq.com>");
MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage);
String result = VelocityEngineUtils.mergeTemplateIntoString(velocityEngine, template, "UTF-8", model);
mimeMessageHelper.setTo(to);
mimeMessageHelper.setFrom(from);
mimeMessageHelper.setSubject(subject);
mimeMessageHelper.setText(result, true);
mailSender.send(mimeMessage);
return true;
} catch (UnsupportedEncodingException e) {
logger.error("发送邮件失败", e.getMessage());
return false;
} catch (javax.mail.MessagingException e) {
logger.error("消息发送异常", e.getMessage());
return false;
}
}
@Override
public void afterPropertiesSet() throws Exception {
mailSender = new JavaMailSenderImpl();
mailSender.setUsername(mailSetting.getUsername());
mailSender.setPassword(mailSetting.getPassword());
mailSender.setHost(mailSetting.getHost());
mailSender.setPort(mailSetting.getPort());
mailSender.setProtocol(mailSetting.getProtocol());
mailSender.setDefaultEncoding(mailSetting.getDefaultEncoding());
Session session = Session.getDefaultInstance(mailSender.getJavaMailProperties(), new Authenticator() {
// 身份认证
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(mailSender.getUsername(), mailSender.getPassword());
}
});
mailSender.setSession(session);
Properties javaProperties = new Properties();
javaProperties.put("mail.stmp.auth",true);
javaProperties.put("mail.stmp.starttls.enable",true);
mailSender.setJavaMailProperties(javaProperties);
}
}
````
+ `mailSetting.properties`邮件配置文件信息
````
一下属性自行更改
mail.username=demo@vip.qq.com mail.password=demo@vip.qq.com mail.host=smtp.qq.com mail.port=465 mail.protocol=smtps mail.defaultEncoding=utf8 ````
2.5Redis事物
以前只知道在关系型数据库里面是有事物的概念的,没想到在 Redis中也是有事物的概念的.很多时候一些重要的操作如果没有事物进行控制的话,会造成很多不必要的损失
在Redis事物要用到的两个命令:
-
MULTI命令
首先在事物开始的时候,先发送
MULTI
命令给Redis,表示开始事物,然后再进行一些数据的操作. -
EXEC命令
在操作全部完成后发送
EXEC
命令告诉Redis事物已经结束了,可以执行了
在事物中间执行的命令都是已经保存在Reids中,但是没有被执行,直到收到
EXEC
命令,然后才会以此开始以此执行服务器中保存的命令.当中只要有一次命令执行失败了的话,其他的指定命令也会被撤销.执行原则就是"要么全部被执行成功,要么全部不执行"
在
Java
中使用
Jedis
操作事物:
java
try {
Transaction tx = jedis.multi();
tx.zadd("test", 2, "1");
tx.zadd("demo", 3, "2");
List<Object> objs = tx.exec();
tx.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
}
2.6排序算法
排序算法这东西我不熟悉,只能说是了解一下,确实有点难度....
- Hacker News
- StackOverflow
- IMDB
2.7timeline
顾名思义就是时间轴的意思,在sns网站中一般是要有互动的,然后就会有各种各样的个人时间轴时间或者是新鲜事.在大量用户会产生大量数据的情况下,对数据的储存我们需要合理地分配储存位置以及储存方式,让数据库性能最优化.
-
数据储存方案:
-
高热点数据放到Redis之类的非关系型数据库,便于高速读取
-
用户言论等数据储存到关系型数据库,便于持久储存,安全性相对更高
-
消息推送方案:
-
这里我们采用推拉的模式,活跃用户直接推送消息,不活跃用户采用拉取的方式,合理利用服务器资源
2.8爬虫
这里数据的读取采用Python进行爬取,主要是Python做一件事的代码量,前提是抛开别的因素.当我写第一个简单爬虫代码的时候我都惊讶到了,操作数据库短短两三行代码.非常的简洁,又有点自然语言的特点.
采用的是
Pyspider
框架,包管理器使用
pip
,
IDE
用
IDEA
的兄弟
Pycharm
2.9压力测试
这里介绍的是一个阿帕奇下的一个开源工具
apache2-utils
,因为我是在
Ubuntu
系统下,直接从终端安装就好了
sudo apt-get install apache2-utils
然后直接在终端里面就可以打开,输入命令
ab
后面带参数
ab [options] [http[s]://]hostname[:port]/path
就可以开始进行测试了,你可以随便输入下,然后就会有帮助出来,跟着帮助做做例子就知道怎么用了,不过这里需要注意的是网址的填写,填写简短的网址的话肯定运行不了的,网址最后面那个要带到来,例如对鹅厂测试下:
```` ab -n 100 -c 100 http://qq.com/ This is ApacheBench, Version 2.3 <$Revision: 1706008 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking qq.com (be patient).....done
Server Software: squid/3.5.20 Server Hostname: qq.com Server Port: 80
Document Path: / Document Length: 160 bytes
Concurrency Level: 100 Time taken for tests: 0.107 seconds Complete requests: 100 Failed requests: 0 Non-2xx responses: 100 Total transferred: 45222 bytes HTML transferred: 16000 bytes Requests per second: 930.95 [#/sec] (mean) Time per request: 107.417 [ms] (mean) Time per request: 1.074 [ms] (mean, across all concurrent requests) Transfer rate: 411.13 [Kbytes/sec] received
Connection Times (ms) min mean[+/-sd] median max Connect: 14 23 6.7 21 32 Processing: 14 28 13.5 26 76 Waiting: 14 28 13.5 26 76 Total: 29 51 17.4 52 107
Percentage of the requests served within a certain time (ms) 50% 52 66% 61 75% 62 80% 63 90% 73 95% 87 98% 97 99% 107 100% 107 (longest request)
````
三. 项目的运行
-
下载
Download Zip
或者
git clone
shell
git clone https://github.com/Sunybyjava/wenda.git
- ### 导入到IDE
使用
IDEA
创建的项目,配置好
maven
的相关配置,以及项目
JDK
版本,直接在主界面选择
Open
,然后找到项目所在路径,点击导入就可以了
-
邮件发送配置
在 resources 下自行配置邮件发送的一些账号信息文件mailSetting.properties
,这里提供参考信息
````properties
以下属性自行更改
mail.username=demo@vip.qq.com // 账号名字 mail.password=demo@vip.qq.com // 账号密码 mail.host=smtp.qq.com // 邮件服务器 mail.port=465 // 邮件端口 mail.protocol=smtps // 邮件协议 mail.defaultEncoding=utf8 // 编码 ````
-
数据库的建立
数据库文件全部位于 resources/sql 目录下面,导入数据库后可以自行填充数据
四.项目问题总结
4.1数据库手动写sql语句注意
- 注释 COMMENT后使用单引号'
- 字段,表名需要括起来就是用反单引号`,也就是数字键1旁边的键位
4.2无法插入数据问题
sql> INSERT INTO question (id, title, content, user_id, created_date, comment_count)
VALUES (
NULL, "如何看待知乎的行为", '无法看待', 10000, CURRENT_TIMESTAMP(), 55)
[2017-09-02 23:40:08] [HY000][1366] Incorrect string value: '\xE5\xA6\x82\xE4\xBD\x95...' for column 'title' at row 1
[2017-09-02 23:40:08] [HY000][1366] Incorrect string value: '\xE6\x97\xA0\xE6\xB3\x95...' for column 'content' at row 1
[2017-09-02 23:40:08] [HY000][1366] Incorrect string value: '\xE5\xA6\x82\xE4\xBD\x95...' for column 'title' at row 1
解决办法: 看起来就是应该是中文才插不进去,然后我想起课程里面的sq代码是没有指定字符集的,而是默认的字符集,首先先选择需要更改编码的数据,然后使用mysql的查看字符集指令:
`show variables like 'character%';
我这边跑出来的是一下的结果
+--------------------------+----------------------------+
| Variable_name | Value |
+--------------------------+----------------------------+
| character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | utf8 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | latin1 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
我们可以看到
character_set_server
的编码是
latin1
,然后我们先修改编码,使用
set character_set_server =utf8
更改
character_set_server
的编码,然后查看结果
+--------------------------+----------------------------+
| Variable_name | Value |
+--------------------------+----------------------------+
| character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | utf8 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | utf8 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
可以看到这里是已经更改好了的,然后我继续插入数据,还是失败了,只有手动删表,然后再建表的时候自己设置下编码,根据资料里面的SQL脚本为
sql
CREATE TABLE `question` (
`id` INT NOT NULL AUTO_INCREMENT,
`title` VARCHAR(255) NOT NULL,
`content` TEXT NULL,
`user_id` INT NOT NULL,
`created_date` DATETIME NOT NULL,
`comment_count` INT NOT NULL,
PRIMARY KEY (`id`),
INDEX `date_index` (`created_date` ASC)
)
我这里需改为下面的之后再重新插入是没用问题的
sql
CREATE TABLE `question` (
`id` INT NOT NULL AUTO_INCREMENT,
`title` VARCHAR(255) NOT NULL,
`content` TEXT NULL,
`user_id` INT NOT NULL,
`created_date` DATETIME NOT NULL,
`comment_count` INT NOT NULL,
PRIMARY KEY (`id`),
INDEX `date_index` (`created_date` ASC)
)
ENGINE = InnoDB
DEFAULT CHARSET = UTF8 COMMENT '问题表';
增加的内容为:
-
ENGINE = InnoDB
:数据库引擎,
InnoDB
是支持事物,外键关联,行级锁的
-
DEFAULT CHARSET = UTF8
:手动设置数据库编码,不适用默认编码
4.3Chrome提交表单报错
An invalid form control with name='' is not focusable.
4.4拦截器中无法注入bean
原来的代码 把原来的构造方法注入修改成以下的代码就可以正常注入,
```java @Component public class PassportInterceptor implements HandlerInterceptor {
@Autowired
private LoginTicketDAO loginTicketDAO;
@Autowired
private UserDAO userDAO;
@Autowired
private HostHolder hostHolder;
```
4.5sql语句报错
在mapper中的写法是这样,注意看 from 跟 where 前面是没有空格的
java
@Select({"select", SELECT_FIELDS + "from " + TABLE_NAME + "where ticket=#{ticket}"})
控制台报错出来的语句
```
SQL: select id user_id, expired, status, ticketfrom login_ticketwhere ticket=?
```
不是我们预想的那样,于是把mapper中的语句改成下面这样就不报错了
java
@Select({"select", SELECT_FIELDS + " from " + TABLE_NAME + " where ticket=#{ticket}"})
4.6jdbc执行Sql语句时报错
报错信息:
[42000][1055] Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'tt.from_id' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
这个报错嘻嘻中提取关键词就是最后那个组合的单词
sql_mode=only_full_group_by
,直接去网上找答案吧!反正我也没碰过这个错误,首先看看MYSQL官方文档里面写的东西,这里就是说在版本5.7默认的模式就有这几个,看到了我们出错的关键词,然后就是在MySQL 5.7.5的时候默认添加了
ONLY_FULL_GROUP_BY
and
STRICT_TRANS_TABLES
两个模式,探究其中的原理的话很多博客文档都有详细的解释
参考链接接:
[MYSQL官方关于sql-mode文档](https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html)
[CSDN某博主比较详细的解释](http://blog.csdn.net/allen_tsang/article/details/54892046)
[stackoverflow上关于disable-only-full-group-by的帖子](https://stackoverflow.com/questions/23921117/disable-only-full-group-by)
-
报错的SQL代码:
sql SELECT from_id, to_id, content, has_read, conversation_id, create_date, count(id) AS id FROM (SELECT * FROM message WHERE from_id = 10000 OR to_id = 10000 ORDER BY id DESC) tt GROUP BY conversation_id
-
登录数据库控制台查看模式
sql SELECT @@global.sql_mode
如果你确定关闭这个模式不会对你的项目造成问题的话,你可以选择关闭ONLY_FULL_GROUP_BY
限制,请注意关闭后的风险!!!sql set sql_mode ='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';
4.7发送邮件失败
```` Exception in thread "Thread-4" org.springframework.mail.MailSendException: Mail server connection failed; nested exception is javax.mail.MessagingException: Could not connect to SMTP host: localhost, port: 465; nested exception is: java.net.ConnectException: Connection refused. Failed messages: javax.mail.MessagingException: Could not connect to SMTP host: localhost, port: 465; nested exception is: java.net.ConnectException: Connection refused; message exception details (1) are: Failed message 1: javax.mail.MessagingException: Could not connect to SMTP host: localhost, port: 465; nested exception is: java.net.ConnectException: Connection refused at com.sun.mail.smtp.SMTPTransport.openServer(SMTPTransport.java:1961) at com.sun.mail.smtp.SMTPTransport.protocolConnect(SMTPTransport.java:654) at javax.mail.Service.connect(Service.java:295) at org.springframework.mail.javamail.JavaMailSenderImpl.connectTransport(JavaMailSenderImpl.java:501) at org.springframework.mail.javamail.JavaMailSenderImpl.doSend(JavaMailSenderImpl.java:421) at org.springframework.mail.javamail.JavaMailSenderImpl.send(JavaMailSenderImpl.java:345) at org.springframework.mail.javamail.JavaMailSenderImpl.send(JavaMailSenderImpl.java:340) at com.suny.utils.MailSender.sendWithHTMLTemplate(MailSender.java:55) at com.suny.async.handler.LoginExceptionHandler.doHandle(LoginExceptionHandler.java:33) at com.suny.async.EventConsumer$1.run(EventConsumer.java:78) at java.lang.Thread.run(Thread.java:745) Caused by: java.net.ConnectException: Connection refused at java.net.PlainSocketImpl.socketConnect(Native Method) at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350) at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206) at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188) at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) at java.net.Socket.connect(Socket.java:589) at java.net.Socket.connect(Socket.java:538) at com.sun.mail.util.SocketFetcher.createSocket(SocketFetcher.java:321) at com.sun.mail.util.SocketFetcher.getSocket(SocketFetcher.java:237) at com.sun.mail.smtp.SMTPTransport.openServer(SMTPTransport.java:1927) ... 10 more
````
看这个就直接去看邮件服务器信息是否错误,我的是配置的信息错误
4.8邮件认证失败
```` Exception in thread "Thread-4" org.springframework.mail.MailAuthenticationException: Authentication failed; nested exception is javax.mail.AuthenticationFailedException: 535 Error: ¨êé: http://service.mail.qq.com/cgi-bin/help?subtype=1&&id=28&&no=1001256
at org.springframework.mail.javamail.JavaMailSenderImpl.doSend(JavaMailSenderImpl.java:424)
at org.springframework.mail.javamail.JavaMailSenderImpl.send(JavaMailSenderImpl.java:345)
at org.springframework.mail.javamail.JavaMailSenderImpl.send(JavaMailSenderImpl.java:340)
at com.suny.utils.MailSender.sendWithHTMLTemplate(MailSender.java:55)
at com.suny.async.handler.LoginExceptionHandler.doHandle(LoginExceptionHandler.java:33)
at com.suny.async.EventConsumer$1.run(EventConsumer.java:78)
at java.lang.Thread.run(Thread.java:745)
Caused by: javax.mail.AuthenticationFailedException: 535 Error: ¨êé: http://service.mail.qq.com/cgi-bin/help?subtype=1&&id=28&&no=1001256
at com.sun.mail.smtp.SMTPTransport$Authenticator.authenticate(SMTPTransport.java:826)
at com.sun.mail.smtp.SMTPTransport.authenticate(SMTPTransport.java:761)
at com.sun.mail.smtp.SMTPTransport.protocolConnect(SMTPTransport.java:685)
at javax.mail.Service.connect(Service.java:295)
at org.springframework.mail.javamail.JavaMailSenderImpl.connectTransport(JavaMailSenderImpl.java:501)
at org.springframework.mail.javamail.JavaMailSenderImpl.doSend(JavaMailSenderImpl.java:421)
... 6 more
````
原因 :邮件来源地址必须跟你发送邮件的地址是一模一样的,否则报错
java
public boolean sendWithHTMLTemplate(String to, String subject,
String template, Map<String, Object> model) {
try {
String nick = MimeUtility.encodeText("邮件通知");
// 下面这个邮箱地址必须跟发送邮件的地址一模一样,否则报错
InternetAddress from = new InternetAddress(nick + "<demo@vip.qq.com>");
MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage);
String result = VelocityEngineUtils.mergeTemplateIntoString(velocityEngine, template, "UTF-8", model);
mimeMessageHelper.setTo(to);
mimeMessageHelper.setFrom(from);
mimeMessageHelper.setSubject(subject);
mimeMessageHelper.setText(result, true);
mailSender.send(mimeMessage);
return true;
} catch (UnsupportedEncodingException e) {
logger.error("发送邮件失败", e.getMessage());
return false;
} catch (javax.mail.MessagingException e) {
logger.error("消息发送异常", e.getMessage());
return false;
}
}
解决方法
:当然是把配置文件中的发件邮箱地址跟
InternetAddress()
方法中传入的邮箱地址一致即可
五.项目经验总结
在这个项目中还是学到了挺多的知识点,很感谢叶神的辛苦付出,总结起来项目基本的模型写好以后含有的知识点还是非常的多的,然后我自己也在慢慢地想完善这个项目,总结出一些刚学习到的一些新的知识点,包括但不限于技巧,框架,技术点,语法等
-
Velocity模板引擎
这个模板引擎还是第一次接触,第一次看到这个还是在《深入分析JAVAWEB内幕》里面看到的,当时看到这个词是介绍淘宝里面应该是在使用或者是早起使用了它,以前自己也只是带学习性用过一下
FreeMarker
,然后网上也出现了许多的模板引擎,我的理解就是大多数模板引擎的出现是为了使用户界面与业务数据进行分离,jsp在用户访问量大的时候容易出现解析时间久,用户体验性不好等等原因,使用起来其实还是挺方便的,至于性能的话我也没有去测试
-
Spring Boot
貌似最近Spring Boot
挺火的,还跟微服务,SpringCould等高大上名词的扯上了关系.总结起来就是Spring的发展导致便携性非常高,然后各种配置文件很繁琐,于是又回到了注解的开发,大幅度减少无关于业务的代码量.这个倒确实有点体会,各种Spring的配置文件手动编写起来很是麻烦.总结出特点就是:- 创建独立的Spring应用程序
- 嵌入的Tomcat,无需部署WAR文件
- 简化Maven配置
- 自动配置Spring
- 提供生产就绪型功能,如指标,健康检查和外部配置
- 绝对没有代码生成和对XML没有要求配置
参考文献
- 基于SSM框架的网上商城系统的设计与实现(西安电子科技大学·薄志强)
- 基于轻量级框架的互动问答平台的设计与实现(中国地质大学(北京)·孙晓峰)
- 基于轻量级框架的互动问答平台的设计与实现(中国地质大学(北京)·孙晓峰)
- 基于微服务架构的问答平台的设计与实现(东南大学·陈强)
- 企业知识分享平台的设计与实现(北京交通大学·胡屹强)
- 基于SSH架构的个人空间交友网站的设计与实现(北京邮电大学·隋昕航)
- 企业知识分享平台的设计与实现(北京交通大学·胡屹强)
- 企业知识分享平台的设计与实现(北京交通大学·胡屹强)
- 基于ASP.NET开发技术的BBS论坛研究与设计(中国海洋大学·马章勤)
- 基于区块链的电商诚信问答关键技术研究(山东财经大学·李港龙)
- 基于创新知识的创新方法问答社区相似问题检测方法研究与应用(山东大学·姜涛)
- 基于Web的信息发布与信息交流平台的设计与实现(吉林大学·许昭霞)
- 基于JSP锦州师范高等专科学校的在线答疑系统的设计与实现(吉林大学·杨文硕)
- 社会化问答社区用户知识隐藏行为影响因素研究(华中师范大学·许小庆)
- 基于区块链的电商诚信问答关键技术研究(山东财经大学·李港龙)
本文内容包括但不限于文字、数据、图表及超链接等)均来源于该信息及资料的相关主题。发布者:代码导航 ,原文地址:https://bishedaima.com/yuanma/35609.html