项目学习总结

单节内容

第一节
开发工具和Java语言介绍
主要介绍项目所需要的开发工具,并且会简单回顾这个项目所用到的语言-java,语法基础,控制流,数据结构,面向对象,异常,随机数等。

第二节
Spring入门,模板语法和渲染
主要结合Spring进行入门介绍,包括参数解析,HTTP Method,AOP等等。
第三节
数据库交互MyBatis集成
主要对业务的字段进行设计,数据库的创建,数据库的交互等,并会介绍注解和XML定义以及首页的开发。
第四节
用户注册登录管理
主要实现注册,登录,浏览等基本功能,并且会考虑到数据安全性等。
第五节
资讯发布,图片上传,资讯首页
主要实现资讯的发布,图片的上传,完成资讯首页的搭建。
第六节
评论中心,站内信
主要搭建资讯详情页,实现评论,站内信等功能。
第七节
redis入门以及redis实现赞踩功能
主要讲解Redis,带你入门以及redis实现赞踩功能。
第八节
异步设计和站内邮件通知系统
主要进行异步涉及和搭建站内邮件通知系统,实现邮件的发送功能。
第九节
多种资讯排序算法 2
主要讲述多种资讯排序算法以及多线程的讲解。
第十节
JavaWeb项目测试和部署,课程总结回顾
主要进行整个项目的测试以及打包,部署,并且会对整个项目进行一个总结和扩展,讲述面试中怎样讲解项目以及包装。

源码分析

结构

1. aspect

1.1 LogAspect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Aspect
@Component
public class LogAspect {
private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);

@Before("execution(* com.nowcoder.controller.*Controller.*(..))")
public void beforeMethod(JoinPoint joinPoint) {
StringBuilder sb = new StringBuilder();
for (Object arg : joinPoint.getArgs()) {
sb.append("arg:" + arg.toString() + "|");
}
logger.info("before method: " + sb.toString());
}

@After("execution(* com.nowcoder.controller.IndexController.*(..))")
public void afterMethod(JoinPoint joinPoint) {
logger.info("after method: ");
}
}

1) @Component

使用@Component标记为IOC容器中的组件

2) Logger LOG = LoggerFactory.getLogger()

用Logger工厂获取Logger实例

3) JoinPoint 对象

JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象.
常用api:

方法名 功能
Signature getSignature() 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息
Object[] getArgs() 获取传入目标方法的参数对象
Object getTarget() 获取被代理的对象
Object getThis() 获取代理对象

2. async

async

异步点赞

为什么异步

点赞,回复评论的时候,表面上是赞数增加了,其实还有很多其他的工作要做。比如,对方要收到消息提醒,成就值增加。一些行为会引起一系列连锁反应。如果在点赞时立马处理,会影响程序运行效率。而且会造成代码冗余,比如发布新闻,和回复评论都可以使得成就值增加,如果都跟着写在后面的代码里会把成就值增加这段代码写两遍,所以大型服务需要服务化和异步化。

  • 服务化
    服务化:某一个单独的业务独立成一个工程,提供接口。不只是service层的一个类。
    暴露一些接口,比如数据库服务,如果一个部门要去数据库查询,小公司可能写个SQL语句。对于大公司,需要一个组单独做数据库服务,暴露接口给别的部门用。好处是防止别的部门有数据库权限,数据库单独有一个组维护,出问题找他们运维就好。
  • 异步化
    异步化:用户点赞,用户首先要知道的是这个赞已经点上了。用户提交Oj,用户要立马知道的是代码有没有通过。而后面应该的东西,比如积分增加了,用户不会有立马想知道的需求,如果间隔几秒钟在更新,用户也不会有很大的意见。

概述

在一个网站中,一个业务发生的同时,还有一些后续业务需要发生。

比如点赞,除了完成点赞功能外,还有一系列,比如提醒被点赞的用户等等,为了能够实现这些操作并且不拖慢单纯点赞功能的实现,我们将这些使用异步队列实现。

处理流程如下图:

处理流程

  • Biz(生产)
    Biz为业务部门,理解为点赞的实现,也就是在实现点赞的同时通过EventProducer发送一个事件
  • 进入队列
    这个事件进入队列等待,队列另一头,有一个EventConsumer,不断消费事件
  • EventHandler(消费)
    EventConsumer下面有很多EventHandler,只要EventHandler发现自己需要处理的事件类型,就会进行相应的操作。

优点:①后续业务的实现,不会拖慢主业务。②如果后续业务的服务器挂掉,只要重启,继续从优先队列消费事件即可。

2.1 LikeHandler

记得开启Rrdis—redis-server.exe redis.windows.conf

具体的执行动作,具体的实现类,这个是点赞后要执行的行为,给别人发提醒。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Component
public class LikeHandler implements EventHandler {
@Autowired
MessageService messageService;

@Autowired
UserService userService;

@Override
public void doHandle(EventModel model) {
Message message = new Message();
message.setFromId(3);
//message.setToId(model.getEntityOwnerId());
message.setToId(model.getActorId());
User user = userService.getUser(model.getActorId());
message.setContent("用户" + user.getName()
+ "赞了你的资讯,http://127.0.0.1:8080/news/" + model.getEntityId());
message.setCreatedDate(new Date());
messageService.addMessage(message);
}

@Override
public List<EventType> getSupportEventTypes() {
return Arrays.asList(EventType.LIKE);
}
}

1)参考 Spring@Autowired注解与自动装

Spring 2.5 引入了 @Autowired 注释,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。 通过 @Autowired的使用来消除 set ,get方法。

  1. 事先在 Spring 容器中声明
    Spring 通过一个 BeanPostProcessor 对 @Autowired 进行解析,所以要让 @Autowired 起作用必须事先在 Spring 容器中声明 AutowiredAnnotationBeanPostProcessor Bean。
1
2
<!-- 该 BeanPostProcessor 将自动对标注 @Autowired 的 Bean 进行注入 -->     
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
  1. 修改在原来注入spirng容器中的bean的方法
    修改在原来注入spirng容器中的bean的方法:在域变量上加上标签@Autowired,并且去掉 相应的get 和set方法

  2. 也去掉
    在applicatonContext.xml中 把原来 引用的标签也去掉。

2.2 LoginExceptionHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Component
public class LoginExceptionHandler implements EventHandler {
@Autowired
MessageService messageService;

@Autowired
MailSender mailSender;

@Override
public void doHandle(EventModel model) {
// 判断是否有异常登陆
Message message = new Message();
message.setToId(model.getActorId());
message.setContent("你上次的登陆ip异常");
message.setFromId(3);
message.setCreatedDate(new Date());
messageService.addMessage(message);

Map<String, Object> map = new HashMap<String, Object>();
map.put("username", model.getExt("username"));
mailSender.sendWithHTMLTemplate(model.getExt("email"), "登陆异常", "mails/welcome.html",
map);
}

@Override
public List<EventType> getSupportEventTypes() {
return Arrays.asList(EventType.LOGIN);
}
}

2.3 EventConsumer

解决的问题

如何将活动分发下去给相关的所有handle实现。

步骤

消费活动,在初始化前,先得到Handler接口所有的实现类,遍历实现类。
通过getSupportEventType得到每个实现类对应处理的活动类型。反过来记录在config哈希表中,config中的key是活动的类型,比如说是LIKE,COMMENT,是枚举里的成员,value是一个ArrayList的数组,里面存放的是各种实现方法。见代码中的。当从队列中获得一个活动时,这里用的是从右向外pop()一个活动实体。进行解析。这里的config.get(eventModel.getType())是一个数组,里面存放着所有关于这个活动要执行的实现类。遍历这个数组,开始执行实现类里的方法。

一些注释

  1. implements InitializingBean , @Override afterPropertiesSet()
    InitializingBean 通过实现此接口的afterPropertiesSet()方法记录哪些Event需要哪些handler来处理
  2. implements ApplicationContextAware
    ApplicationContextAware 通过实现此接口的setApplicationContext方法获取上下文
  3. Map<EventType, List<EventHandler>> config = new HashMap<>();
    用来存储一个事件类型对应的所有的eventhandler,下次有该事件产生时,即可直接调用对应的list
  4. Map<String, EventHandler> beans = applicationContext.getBeansOfType(EventHandler.class);
    找出上下文中所有实现了EventHandler接口的类,存入beans
  5. if (beans != null)……
    遍历所有的handler,将他们存入他们所监听的eventType对应的list中
  6. List<String> messages = jedisAdapter.brpop(0, key);
    从redis的队列中取出事件,并存入list中
  7. for (String message : messages)
    遍历取出的事件
    7.1 if (message.equals(key))
    第一个元素是队列名字,跳过
    7.2 if (!config.containsKey(eventModel.getType()))
    跳过不能处理的事件
    7.3 for (EventHandler handler : config.get(eventModel.getType()))
    处理他的所有的handler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@Service
//1------------------------
public class EventConsumer implements InitializingBean, ApplicationContextAware {

private static final Logger logger = LoggerFactory.getLogger(EventConsumer.class);
//2--------------
private Map<EventType, List<EventHandler>> config = new HashMap<>();
private ApplicationContext applicationContext;

@Autowired
private JedisAdapter jedisAdapter;

@Override
public void afterPropertiesSet() throws Exception {
//4----------------------
Map<String, EventHandler> beans = applicationContext.getBeansOfType(EventHandler.class);
//5-----------------------------
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<EventHandler>());
System.out.println("添加一个新的 :" + type);//20180802
}
config.get(type).add(entry.getValue());// 注册每个事件的处理函数
System.out.println("注册每个事件的处理函数 :" + type + " " + entry.getValue()); //20180802
}
}
}

Thread thread = new Thread(new Runnable() {// 启动线程去消费事件
@Override
public void run() {
while (true) {// 从队列一直消费
String key = RedisKeyUtil.getEventQueueKey();
//6------------------------------
List<String> messages = jedisAdapter.brpop(0, key);

for (String message : messages) {
//7.1---------------
if (message.equals(key)) {
continue;
}
EventModel eventModel = JSON.parseObject(message, EventModel.class);
//7.2---------------
System.out.println("找到这个事件的处理handler列表 : " + eventModel.getType()) //20180802
if (!config.containsKey(eventModel.getType())) { //找到这个事件的处理handler列表
logger.error("不能识别的事件");
continue;
}
//7.3---------------
for (EventHandler handler : config.get(eventModel.getType())) {//处理他的所有的handler
System.out.println("处理事件 : " + eventModel.getType() + " " + handler.getClass() );//20180802
handler.doHandle(eventModel);
}
}
}
}
});
thread.start();
}

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}

2.4 EventHandler

设计为一个接口,handler都实现此接口。

1
2
3
4
public interface EventHandler {
void doHandle(EventModel model);//处理此事件
List<EventType> getSupportEventTypes();//添加监视的事件类型
}

2.5 EventModel

即发送的队列的事件模型,只有一些基本属性和get、set方法。

其中一些set的return 设置为this,是因为方便连续set多个属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
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<String, String>();

public EventModel(EventType type) {
this.type = type;
}
public EventModel() {
}

public String getExt(String key) {
return exts.get(key);
}
public EventModel setExt(String key, String value) {
exts.put(key, value);
return this;//方便连续set多个属性。
}

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 void setExts(Map<String, String> exts) {
this.exts = exts;
}
}

2.6 EventProducer

活动生产者,相当于生产消费者中的生产者,在controller层执行一个动作后,用这个类把需要异步的信息打包好,放进Redis的队列中。放入是把EventModel序列化为JSON,存入Redis的列表中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Service
public class EventProducer {
@Autowired
JedisAdapter jedisAdapter;

public boolean fireEvent(EventModel model) {
try {
String json = JSONObject.toJSONString(model);
String key = RedisKeyUtil.getEventQueueKey();
jedisAdapter.lpush(key, json);
System.out.println("产生一个异步事件:" + eventModel.getType());//20180802
return true;
} catch (Exception e) {
return false;
}
}
}

1) JSON

由于 JSON 语法是 JavaScript 语法的子集,JavaScript 函数 eval() 可用于将 JSON 文本转换为 JavaScript 对象。

概览

  • JSON(JavaScript Object Notation)是一种轻量级的数据交换格式。
  • JSON 是存储和交换文本信息的语法。类似 XML。
  • JSON 比 XML 更小、更快,更易解析。

为什么使用 JSON?

对于 AJAX 应用程序来说,JSON 比 XML 更快更易使用:

使用 XML
  • 读取 XML 文档
  • 使用 XML DOM 来循环遍历文档
  • 读取值并存储在变量中
使用 JSON
  • 读取 JSON 字符串
  • eval() 处理 JSON 字符串

书写格式

JSON 数据的书写格式是:名称/值对。
名称/值对包括字段名称(在双引号中),后面写一个冒号,然后是值:
“firstName” : “John”
这很容易理解,等价于这条 JavaScript 语句:
firstName = “John”

json嵌套

对于json嵌套,只要记住

  • 符号”前是键,符号后是值
  • 大括号成对找

一层层剥开,就清楚了。

2)前后台的传输

  • JSON.parseObject,是将Json字符串转化为相应的对象
  • JSON.toJSONString则是将对象转化为Json字符串。

2.7 EventType

EventType 是获得活动的类型,可以有点赞,评论,登陆等待

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public enum EventType {
LIKE(0),
COMMENT(1),
LOGIN(2),
MAIL(3);

private int value;

EventType(int value) {
this.value = value;
}

public int getValue() {
return value;
}
}

3. configuration

3.1. ToutiaoWebConfiguration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component
public class ToutiaoWebConfiguration extends WebMvcConfigurerAdapter {
@Autowired
PassportInterceptor passportInterceptor;

@Autowired
LoginRequiredInterceptor loginRequiredInterceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(passportInterceptor);
registry.addInterceptor(loginRequiredInterceptor).
addPathPatterns("/msg/*").addPathPatterns("/like").addPathPatterns("/dislike");
super.addInterceptors(registry);
}
}

1)spring boot中使用拦截器

1、注册拦截器

创建一个类MyWebConfig继承WebMvcConfigurerAdapter,并重写addInterceptors方法

多个拦截器组成一个拦截器链

  • addPathPatterns
    添加拦截规则
  • excludePathPatterns
    排除拦截
1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class MyWebConfig extends WebMvcConfigurerAdapter {
@Autowired
MyiInterceptor myiInterceptor;

@Override //注册 拦截器
public void addInterceptors(InterceptorRegistry registry) {
//拦截器myiInterceptor只拦截'/111'的请求,不拦截'/helloWorld'
registry.addInterceptor(myiInterceptor).addPathPatterns("/111")
.excludePathPatterns("/helloWorld");
super.addInterceptors(registry);
}
}

2、自定义拦截器

创建一个自定义拦截器MyiInterceptor实现HandlerInterceptor接口重写所有的方法实现自己的业务(见下面的章节–)

4. controller

controller

4.1. HomeController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
@Controller
public class HomeController {
@Autowired
NewsService newsService;

@Autowired
UserService userService;

@Autowired
LikeService likeService;

@Autowired
HostHolder hostHolder;

@Autowired
MailSender mailSender;

private List<ViewObject> getNews(int userId, int offset, int limit) {
List<News> newsList = newsService.getLatestNews(userId, offset, limit);
int localUserId = hostHolder.getUser() != null ? hostHolder.getUser().getId() : 0;
List<ViewObject> vos = new ArrayList<>();
for (News news : newsList) {
ViewObject vo = new ViewObject();
vo.set("news", news);
vo.set("user", userService.getUser(news.getUserId()));
if (localUserId != 0) {
vo.set("like", likeService.getLikeStatus(localUserId, EntityType.ENTITY_NEWS, news.getId()));
} else {
vo.set("like", 0);
}
vos.add(vo);
}
return vos;
}

@RequestMapping(path = {"/", "/index"}, method = {RequestMethod.GET, RequestMethod.POST})
public String index(Model model,
@RequestParam(value = "pop", defaultValue = "0") int pop) {
model.addAttribute("vos", getNews(0, 0, 10));
if (hostHolder.getUser() != null) {
pop = 0;
}
model.addAttribute("pop", pop);
return "home";
}

@RequestMapping(path = {"/user/{userId}"}, method = {RequestMethod.GET, RequestMethod.POST})
public String userIndex(Model model, @PathVariable("userId") int userId) {
model.addAttribute("vos", getNews(userId, 0, 10));
return "home";
}
}

4.2. IndexController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
//@Controller
public class IndexController {
private static final Logger logger = LoggerFactory.getLogger(IndexController.class);

@Autowired
private ToutiaoService toutiaoService;

@RequestMapping(path = {"/", "/index"}, method = {RequestMethod.GET, RequestMethod.POST})
@ResponseBody
public String index(HttpSession session) {
logger.info("Visit Index");
return "Hello NowCoder," + session.getAttribute("msg")
+ "<br> Say:" + toutiaoService.say();
}

@RequestMapping(value = {"/profile/{groupId}/{userId}"})
@ResponseBody
public String profile(@PathVariable("groupId") String groupId,
@PathVariable("userId") int userId,
@RequestParam(value = "type", defaultValue = "1") int type,
@RequestParam(value = "key", defaultValue = "nowcoder") String key) {
return String.format("GID{%s},UID{%d},TYPE{%d},KEY{%s}", groupId, userId, type, key);
}

@RequestMapping(value = {"/vm"})
public String news(Model model) {
model.addAttribute("value1", "vv1");
List<String> colors = Arrays.asList(new String[]{"RED", "GREEN", "BLUE"});

Map<String, String> map = new HashMap<String, String>();
for (int i = 0; i < 4; ++i) {
map.put(String.valueOf(i), String.valueOf(i * i));
}

model.addAttribute("colors", colors);
model.addAttribute("map", map);
model.addAttribute("user", new User("Jim"));

return "news";
}

@RequestMapping(value = {"/request"})
@ResponseBody
public String request(HttpServletRequest request,
HttpServletResponse response,
HttpSession session) {
StringBuilder sb = new StringBuilder();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
sb.append(name + ":" + request.getHeader(name) + "<br>");
}

for (Cookie cookie : request.getCookies()) {
sb.append("Cookie:");
sb.append(cookie.getName());
sb.append(":");
sb.append(cookie.getValue());
sb.append("<br>");
}

sb.append("getMethod:" + request.getMethod() + "<br>");
sb.append("getPathInfo:" + request.getPathInfo() + "<br>");
sb.append("getQueryString:" + request.getQueryString() + "<br>");
sb.append("getRequestURI:" + request.getRequestURI() + "<br>");

return sb.toString();

}

@RequestMapping(value = {"/response"})
@ResponseBody
public String response(@CookieValue(value = "nowcoderid", defaultValue = "a") String nowcoderId,
@RequestParam(value = "key", defaultValue = "key") String key,
@RequestParam(value = "value", defaultValue = "value") String value,
HttpServletResponse response) {
response.addCookie(new Cookie(key, value));
response.addHeader(key, value);
return "NowCoderId From Cookie:" + nowcoderId;
}

@RequestMapping("/redirect/{code}")
public String redirect(@PathVariable("code") int code,
HttpSession session) {
/*
RedirectView red = new RedirectView("/", true);
if (code == 301) {
red.setStatusCode(HttpStatus.MOVED_PERMANENTLY);
}
return red;*/
session.setAttribute("msg", "Jump from redirect.");
return "redirect:/";
}

@RequestMapping("/admin")
@ResponseBody
public String admin(@RequestParam(value = "key", required = false) String key) {
if ("admin".equals(key)) {
return "hello admin";
}
throw new IllegalArgumentException("Key 错误");
}

@ExceptionHandler()
@ResponseBody
public String error(Exception e) {
return "error:" + e.getMessage();
}
}

1)model.addAttribute("k",v)

model.addAttribute(“editPageFlg”,editPageFlg)的作用:类似于hashmap。

向Map里面添加键值对,key=”editPageFlg”,value=editPageFlg。

此类来源于ModelMap的定义,

1
public class ModelMap extends LinkHashMap<String,Object>

其中addAttribute的源码为:

1
2
3
4
5
public ModelMap addAttribute(String attributeName, Object attributeValue){
Assert.notNull(attributeName, "Model attribute name must not be null");
put(attributeName, attributeValue);
return this;
}

在put之前,会进行判空检测。这就是addAttribute与put的区别。

2)@CookieValue

@CookieValue的作用

用来获取Cookie中的值

@CookieValue参数

1、value:参数名称
2、required:是否必须
3、defaultValue:默认值

3)用 JSP 设置 Cookies

用 JSP 设置 Cookies 包括三个步骤:

  • (1) 创建一个 Cookie 对象
    用 cookie 的名称和值调用 cookie 构造函数,名称和值是字符串。
1
Cookie cookie = new Cookie("key","value");

这个名字和值都不应该包含空格或任何以下字符:[ ] ( ) = , " / ? @ : ;

  • (2) 设置最大持续时间
    使用 setMaxAge 指定 cookie 的有效期是多长时间(以秒为单位)。以下是建立了一个持续 24 小时的 cookie。
1
cookie.setMaxAge(60*60*24);
  • (3) 将 cookie 发送到 HTTP 响应标题中
    使用response.addCookie 在 HTTP 响应标题中添加 cookies,如下所示:
1
response.addCookie(cookie);

4)request和response方法的总结

Request

JSP中的隐藏对象 – request

request可以在JSP网页中使用,在转译为Servlet之后,它会转换为javax.servlet.http.HttpServletRequest型态的对象,HttpServletRequest对象是有关于客户端所发出的请求之对象,只要是有关于客户端请求的信息,都可以藉由它来取得,例如请求标头、请求方法、请求参数、使用者IP等等信息。

方法 作用
getParameterNames() 取得客户端所发出的请求参数名称
getParameter() 可以让您指定请求参数名称,以取得对应的设定值
getServerName() 请求的服务器
getProtocol() 使用协议
getMethod() 请求方法
getServerPort() 请求端口号
getContextPath() Context路径. context理解为上下文比较好(也就是一个项目)
getServletPath() Servlet路径
getRequestURI() URI路径
getQueryString() 查询字符串
getRemoteAddr() 使用者主机IP
getRemotePort() 使用者使用端口号

response

JSP中的隐藏对象 –response
JSP的response隐藏对象在转换为Servlet之后,对应于HttpServletResponse型态对象,HttpServletResponse对象是有关于对客户端请求之响应,您可以利用它来设定一些要响应的讯息,例如标题信息、响应状态码等.  
 
|方法|作用|
|—|—|
|setHeader()|是一个通用的标头设定方法,您可以用它来设定任何「名称/值」的标头|
|setIntHeader()|是专门用来设定整数值标头的版本|
|setDateHeader()|是setHeader()的Date设定版本,第二个参数是设定Date的Long数值,0表示GMT 1970/1/1 00:00。
|setStatus()|是用来设定回应的状态码,例如404 Not Found,HttpServletResponse类中提供了一些助忆常数设定,例如SC_NOT_FOUND就是表示404状态码(可以在Servlet API文件中查询相关的助忆常数)|
|sendError()|会根据服务器的预设错误网页回报方式显示错误讯息|
|sendRedirect()|设置重定向页面|
|getWriter()|取得PrintWriter对象,由它来写出响应至服务器的本体信息|

5)HttpServletResponse和HttpServletRequest

HttpServletResponse和HttpServletRequest

请求响应流程图

Request

Request是Servlet.service()方法的一个参数,类型为javax.servlet.http.HttpServletRequest。在客户端发出每个请求时,服务器都会创建一个request对象,并把请求数据封装到request中,然后在调用Servlet.service()方法时传递给service()方法,这说明在service()方法中可以通过request对象来获取请求数据

Request的功能可以分为以下几种:

  • 封装了请求头数据
  • 封装了请求正文数据,如果是GET请求,那么就没有正文
  • request是一个域对象,可以把它当成Map来添加获取数据
  • request提供了请求转发和请求包含功能

Request的一些get...()

Response

Response是Servlet.service方法的一个参数,类型为javax.servlet.http.HttpServletResponse。在客户端发出每个请求时,服务器都会创建一个response对象,并传入给Servlet.service()方法。response对象是用来对客户端进行响应的,这说明在service()方法中使用response对象可以完成对客户端的响应工作

response对象的功能分为以下四种:

  • 设置响应头信息
  • 发送状态码
  • 设置响应正文
  • 重定向

6)numeration接口

Enumeration是java.util中的一个接口类,在Enumeration中封装了有关枚举数据集合的方法,与Iterator差不多,用来遍历集合中的元素 但是枚举Enumeration只提供了遍历Vector和Hashtable类型集合元素的功能,这种类型的集合对象通过调用elements()方法获取一个Enumeration对象 然后Enumeratino对象再调用以下方法来对集合中的元素进行遍历。

1
2
3
4
5
6
package java.util;

public interface Enumeration<E> {
boolean hasMoreElements();
E nextElement();
}
方法 描述
boolean hasMoreElements( ) 测试此枚举是否包含更多的元素
Object nextElement( ) 如果此枚举对象至少还有一个可提供的元素,则返回此枚举的下一个元素

7)Iterator接口

1
2
3
4
5
6
7
package java.util;

public interface Iterator<E> {
boolean hasNext();
E next();
void remove();
}

8) Spring MVC 重定向常用处理方式

Controller 视图方法间的跳转,无非就是带参跳转和不带参跳转。常用的方法有通过 String 映射 RequestMapping实现重定向,或者通过 ModelAndView 对象,又或者是 RedirectView 对象,下面逐一说明。

String 重定向

是 return 映射到另一个 Controller 方法的字符串。如果有请求参数,就拼接在 RequestMapping 映射的字符串后面。

1
2
3
4
5
6
7
// 返回字符串映射的方式
@RequestMapping("hello")
public String hello(HttpServletRequest req, HttpServletResponse resp) {
doSomething();
return "redirect:/bye";
// return "redirect:/bye?username=yxd";
}

ModelAndView 重定向

另一种方法是通过返回 ModelAndView 对象来实现跳转。类似的,如果有请求参数,也可以通过类似 GET 参数拼接的方式:

1
2
3
4
5
6
7
// 返回 ModelAndView 对象
@RequestMapping("hello")
public ModelAndView hello(HttpServletRequest req, HttpServletResponse resp) {
doSomething();
return new ModelAndView("redirect:/bye");
// return new ModelAndView("redirect:/bye?username=yxd");
}

RedirectView 重定向

还有一种方法是通过返回 RedirectView 对象实现跳转,该方法和上面的不同之处在于,RedirectView 对象不需要设置 redirect 前缀:

1
2
3
4
5
6
7
// 返回 RedirectView 对象
@RequestMapping("hello")
public RedirectView hello() {
doSomething();
return new RedirectView("/bye");
// return new RedirectView("bye?username=yxd");
}

4.3. LikeController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@Controller
public class LikeController {
@Autowired
LikeService likeService;

@Autowired
HostHolder hostHolder;

@Autowired
NewsService newsService;

@Autowired
EventProducer eventProducer;

@RequestMapping(path = {"/like"}, method = {RequestMethod.GET, RequestMethod.POST})
@ResponseBody
public String like(@Param("newId") int newsId) {
long likeCount = likeService.like(hostHolder.getUser().getId(), EntityType.ENTITY_NEWS, newsId);
System.out.println("like likecount " + likeCount);//20180802
// 更新喜欢数
News news = newsService.getById(newsId);
newsService.updateLikeCount(newsId, (int) likeCount);

eventProducer.fireEvent(new EventModel(EventType.LIKE)
.setActorId(hostHolder.getUser().getId()).setEntityId(newsId)
.setEntityType(EntityType.ENTITY_NEWS).setEntityOwnerId(news.getUserId()));
//return "redirect:/"; //20180802
return ToutiaoUtil.getJSONString(0, String.valueOf(likeCount));
}

@RequestMapping(path = {"/dislike"}, method = {RequestMethod.GET, RequestMethod.POST})
@ResponseBody
public String dislike(@Param("newId") int newsId) {
long likeCount = likeService.disLike(hostHolder.getUser().getId(), EntityType.ENTITY_NEWS, newsId);

// 更新喜欢数
newsService.updateLikeCount(newsId, (int) likeCount);
//return "redirect:/"; //20180802
return ToutiaoUtil.getJSONString(0, String.valueOf(likeCount));
}
}

4.4. LoginController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
@Controller
public class LoginController {
private static final Logger logger = LoggerFactory.getLogger(LoginController.class);

@Autowired
UserService userService;

@Autowired
EventProducer eventProducer;

@RequestMapping(path = {"/reg/"}, method = {RequestMethod.GET, RequestMethod.POST})
@ResponseBody
public String reg(Model model, @RequestParam("username") String username,
@RequestParam("password") String password,
@RequestParam(value = "rember", defaultValue = "0") int rememberme,
HttpServletResponse response) {
logger.info("/reg/ " + username + " " + password + " " + rememberme);//20180802
try {
Map<String, Object> map = userService.register(username, password);
if (map.containsKey("ticket")) {
Cookie cookie = new Cookie("ticket", map.get("ticket").toString());
cookie.setPath("/");
if (rememberme > 0) {
cookie.setMaxAge(3600 * 24 * 5);
}
response.addCookie(cookie);
logger.info("cookie : " + cookie.getValue() +" " + cookie.getName());//20180802
//return "redirect:/";
return ToutiaoUtil.getJSONString(0, "注册成功");
} else {
model.addAttribute("error","注册失败,请重新注册");//20180802
//return "redirect:/register";//20180802
return ToutiaoUtil.getJSONString(1, map);
}

} catch (Exception e) {
logger.error("注册异常" + e.getMessage());
model.addAttribute("error","注册异常");//20180802
//return "redirect:/register";//20180802
return ToutiaoUtil.getJSONString(1, "注册异常");
}
}

// @RequestMapping(value = "/login",method = RequestMethod.GET)
// public String login(){
//
// return "login";
// }
//
// @RequestMapping(value = "/register",method = RequestMethod.GET)
// public String register(){
//
// return "register";
// }

@RequestMapping(path = {"/login/"}, method = {RequestMethod.GET, RequestMethod.POST})
@ResponseBody
public String login(Model model, @RequestParam("username") String username,
@RequestParam("password") String password,
@RequestParam(value = "rember", defaultValue = "0") int rememberme,
HttpServletResponse response) {
try {
Map<String, Object> map = userService.login(username, password);
if (map.containsKey("ticket")) {
Cookie cookie = new Cookie("ticket", map.get("ticket").toString());
cookie.setPath("/");
if (rememberme > 0) {
cookie.setMaxAge(3600 * 24 * 5);
}
response.addCookie(cookie);
eventProducer.fireEvent(new EventModel(EventType.LOGIN)
.setActorId((int) map.get("userId"))
.setExt("username", username).setExt("email", "1751341161@qq.com"));
//return "redirect:/";//20180802
return ToutiaoUtil.getJSONString(0, "成功");
} else {
model.addAttribute("error","登录失败,请重新登录!");//20180802
//return "redirect:/login";//20180802
return ToutiaoUtil.getJSONString(1, map);
}
} catch (Exception e) {
logger.error("注册异常" + e.getMessage());
model.addAttribute("error","登录异常");//20180802
//return "redirect:/login";//20180802
return ToutiaoUtil.getJSONString(1, "注册异常");
}
}

@RequestMapping(path = {"/logout/"}, method = {RequestMethod.GET, RequestMethod.POST})
public String logout(@CookieValue("ticket") String ticket) {
userService.logout(ticket);
logger.info("logout:跳转到首页");//20180802
return "redirect:/";
}
}

4.5 MessageController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
@Controller
public class MessageController {
private static final Logger logger = LoggerFactory.getLogger(MessageController.class);

@Autowired
MessageService messageService;

@Autowired
UserService userService;

@Autowired
HostHolder hostHolder;

@RequestMapping(path = {"/msg/list"}, method = {RequestMethod.GET})
public String conversationDetail(Model model) {
try {
int localUserId = hostHolder.getUser().getId();
List<ViewObject> conversations = new ArrayList<ViewObject>();
List<Message> conversationList = messageService.getConversationList(localUserId, 0, 10);
for (Message msg : conversationList) {
ViewObject vo = new ViewObject();
vo.set("conversation", msg);
int targetId = msg.getFromId() == localUserId ? msg.getToId() : msg.getFromId();
User user = userService.getUser(targetId);
vo.set("user", user);
vo.set("unread", messageService.getConvesationUnreadCount(localUserId, msg.getConversationId()));
conversations.add(vo);
}
model.addAttribute("conversations", conversations);
//return "letter";//20180802
} catch (Exception e) {
logger.error("获取站内信列表失败" + e.getMessage());
}
return "letter";
}

@RequestMapping(path = {"/msg/detail"}, method = {RequestMethod.GET})
public String conversationDetail(Model model, @Param("conversationId") String conversationId) {
try {
List<Message> conversationList = messageService.getConversationDetail(conversationId, 0, 10);
List<ViewObject> messages = new ArrayList<>();
for (Message msg : conversationList) {
ViewObject vo = new ViewObject();
vo.set("message", msg);
User user = userService.getUser(msg.getFromId());
if (user == null) {
continue;
}
vo.set("headUrl", user.getHeadUrl());
vo.set("userId", user.getId());
messages.add(vo);
}
model.addAttribute("messages", messages);
} catch (Exception e) {
logger.error("获取详情消息失败" + e.getMessage());
}
return "letterDetail";
}


@RequestMapping(path = {"/msg/addMessage"}, method = {RequestMethod.POST})
@ResponseBody
public String addMessage(@RequestParam("fromId") int fromId,
@RequestParam("toId") int toId,
@RequestParam("content") String content) {
try {
Message msg = new Message();
msg.setContent(content);
msg.setFromId(fromId);
msg.setToId(toId);
msg.setCreatedDate(new Date());
//msg.setConversationId(fromId < toId ? String.format("%d_%d", fromId, toId) : String.format("%d_%d", toId, fromId));
messageService.addMessage(msg);
return ToutiaoUtil.getJSONString(msg.getId());
} catch (Exception e) {
logger.error("增加评论失败" + e.getMessage());
return ToutiaoUtil.getJSONString(1, "插入评论失败");
}
}
}

4.6 NewsController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
@Controller
public class NewsController {
private static final Logger logger = LoggerFactory.getLogger(NewsController.class);
@Autowired
NewsService newsService;

@Autowired
QiniuService qiniuService;

@Autowired
HostHolder hostHolder;

@Autowired
UserService userService;

@Autowired
CommentService commentService;

@Autowired
LikeService likeService;

@RequestMapping(path = {"/news/{newsId}"}, method = {RequestMethod.GET})
public String newsDetail(@PathVariable("newsId") int newsId, Model model) {
News news = newsService.getById(newsId);
if (news != null) {
int localUserId = hostHolder.getUser() != null ? hostHolder.getUser().getId() : 0;
if (localUserId != 0) {
model.addAttribute("like", likeService.getLikeStatus(localUserId, EntityType.ENTITY_NEWS, news.getId()));
} else {
model.addAttribute("like", 0);
}
// 评论
List<Comment> comments = commentService.getCommentsByEntity(news.getId(), EntityType.ENTITY_NEWS);
List<ViewObject> commentVOs = new ArrayList<ViewObject>();
for (Comment comment : comments) {
ViewObject vo = new ViewObject();
vo.set("comment", comment);
vo.set("user", userService.getUser(comment.getUserId()));
commentVOs.add(vo);
}
model.addAttribute("comments", commentVOs);
}
model.addAttribute("news", news);
model.addAttribute("owner", userService.getUser(news.getUserId()));
return "detail";
}

@RequestMapping(path = {"/addComment"}, method = {RequestMethod.POST})
public String addComment(@RequestParam("newsId") int newsId,
@RequestParam("content") String content) {
try {
content = HtmlUtils.htmlEscape(content);
// 过滤content
Comment comment = new Comment();
comment.setUserId(hostHolder.getUser().getId());
comment.setContent(content);
comment.setEntityId(newsId);
comment.setEntityType(EntityType.ENTITY_NEWS);
comment.setCreatedDate(new Date());
comment.setStatus(0);

commentService.addComment(comment);
// 更新news里的评论数量
int count = commentService.getCommentCount(comment.getEntityId(), comment.getEntityType());
newsService.updateCommentCount(comment.getEntityId(), count);
// 怎么异步化
} catch (Exception e) {
logger.error("增加评论失败" + e.getMessage());
}
return "redirect:/news/" + String.valueOf(newsId);
}


@RequestMapping(path = {"/image"}, method = {RequestMethod.GET})
@ResponseBody
public void getImage(@RequestParam("name") String imageName,
HttpServletResponse response) {
try {
response.setContentType("image/jpeg");
StreamUtils.copy(new FileInputStream(new
File(ToutiaoUtil.IMAGE_DIR + imageName)), response.getOutputStream());
} catch (Exception e) {
logger.error("读取图片错误" + imageName + e.getMessage());
}
}

@RequestMapping(path = {"/uploadImage/"}, method = {RequestMethod.POST})
@ResponseBody
public String uploadImage(@RequestParam("file") MultipartFile file) {
try {
String fileUrl = newsService.saveImage(file);
//String fileUrl = qiniuService.saveImage(file);
if (fileUrl == null) {
return ToutiaoUtil.getJSONString(1, "上传图片失败");
}
return ToutiaoUtil.getJSONString(0, fileUrl);
} catch (Exception e) {
logger.error("上传图片失败" + e.getMessage());
return ToutiaoUtil.getJSONString(1, "上传失败");
}
}

@RequestMapping(path = {"/user/addNews/"}, method = {RequestMethod.POST})
@ResponseBody
public String addNews(@RequestParam("image") String image,
@RequestParam("title") String title,
@RequestParam("link") String link) {
try {
News news = new News();
news.setCreatedDate(new Date());
news.setTitle(title);
news.setImage(image);
news.setLink(link);
if (hostHolder.getUser() != null) {
news.setUserId(hostHolder.getUser().getId());
} else {
// 设置一个匿名用户
news.setUserId(3);
}
newsService.addNews(news);
return ToutiaoUtil.getJSONString(0);
} catch (Exception e) {
logger.error("添加资讯失败" + e.getMessage());
return ToutiaoUtil.getJSONString(1, "发布失败");
}
}
}

4.7. SettingController

1
2
3
4
5
6
7
8
@Controller
public class SettingController {
@RequestMapping("/setting")
@ResponseBody
public String setting() {
return "Setting:OK";
}
}

1)@RequestBody

  • 1.不使用@RequestBody注解
    相当于使用该注解,并required默认为true
    ①当前端没有传参时,会报错400
    ②当前端传一个空{},对象会根据构造函数自动生成。
  • 2.只使用@RequestBody注解
    效果同1
  • 3.使用@RequestBody(required = false)
    可以不传参,但句柄会为null

2)@ResponseBody

  • 作用:
    该注解用于将Controller的方法返回的对象,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区。
  • 使用时机:
    返回的数据不是html标签的页面,而是其他某种格式的数据时(如json、xml等)使用;

5. DAO

DAO

5.1 CommentDAO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Mapper
public interface CommentDAO {
String TABLE_NAME = " comment ";
String INSERT_FIELDS = " user_id, content, created_date, entity_id, entity_type, status ";
String SELECT_FIELDS = " id, " + INSERT_FIELDS;

@Insert({"insert into ", TABLE_NAME, "(", INSERT_FIELDS,
") values (#{userId},#{content},#{createdDate},#{entityId},#{entityType},#{status})"})
int addComment(Comment comment);

@Update({"update ", TABLE_NAME, " set status=#{status} where entity_id=#{entityId} and entity_type=#{entityType}"})
void updateStatus(@Param("entityId") int entityId, @Param("entityType") int entityType, @Param("status") int status);

@Select({"select ", SELECT_FIELDS, " from ", TABLE_NAME,
" where entity_id=#{entityId} and entity_type=#{entityType} order by id desc"})
List<Comment> selectByEntity(@Param("entityId") int entityId, @Param("entityType") int entityType);

@Select({"select count(id) from ", TABLE_NAME, " where entity_id=#{entityId} and entity_type=#{entityType} "})
int getCommentCount(@Param("entityId") int entityId, @Param("entityType") int entityType);
}

1)DAO在JavaWeb开发中的定位

DAO在JavaWeb开发中的定位

一个典型的DAO实现有下列几个组件:
1.一个DAO接口(CRUD)
2.一个实现DAO接口的具体类
3.数据传递对象(DTO)(POJO):有些时候叫做值对象(VO)或领域模型(domain)

2)@Mapper注解

mybatis对java自定义注解的使用——入门篇

作用:持久化

  • 一种持久化方式:
    在一个包中写一个DAO的接口,在另一个包里面写DAO的实现,使用sqlMapClient来从***-sql.xml中读取相应的sql。
  • spring+Mybatis的项目,一种新的持久化方式:
    只写一个dao的接口,在接口的方法中直接注解上用到的sql语句,接口上方多了一个@Mapper注解

5.2 LoginTicketDAO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Mapper
public interface LoginTicketDAO {
String TABLE_NAME = "login_ticket";
String INSERT_FIELDS = " user_id, expired, status, ticket ";
String SELECT_FIELDS = " id, " + INSERT_FIELDS;

@Insert({"insert into ", TABLE_NAME, "(", INSERT_FIELDS,
") values (#{userId},#{expired},#{status},#{ticket})"})
int addTicket(LoginTicket ticket);

@Select({"select ", SELECT_FIELDS, " from ", TABLE_NAME, " where ticket=#{ticket}"})
LoginTicket selectByTicket(String ticket);

@Update({"update ", TABLE_NAME, " set status=#{status} where ticket=#{ticket}"})
void updateStatus(@Param("ticket") String ticket, @Param("status") int status);
}

5.3 MessageDAO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Mapper
public interface MessageDAO {
String TABLE_NAME = " message ";
String INSERT_FIELDS = " from_id, to_id, content, has_read, conversation_id, created_date ";
String SELECT_FIELDS = " id, " + INSERT_FIELDS;

@Insert({"insert into ", TABLE_NAME, "(", INSERT_FIELDS,
") values (#{fromId},#{toId},#{content},#{hasRead},#{conversationId},#{createdDate})"})
int addMessage(Message message);

@Select({"select ", SELECT_FIELDS, " from ", TABLE_NAME, " where conversation_id=#{conversationId} order by id desc limit #{offset}, #{limit}"})
List<Message> getConversationDetail(@Param("conversationId") String conversationId,
@Param("offset") int offset, @Param("limit") int limit);

@Select({"select count(id) from ", TABLE_NAME, " where has_read=0 and to_id=#{userId} and conversation_id=#{conversationId}"})
int getConvesationUnreadCount(@Param("userId") int userId, @Param("conversationId") String conversationId);

@Select({"select ", INSERT_FIELDS, " ,count(id) as id from ( select * from ", TABLE_NAME, " where from_id=#{userId} or to_id=#{userId} order by id desc) tt group by conversation_id order by created_date desc limit #{offset}, #{limit}"})
List<Message> getConversationList(@Param("userId") int userId,
@Param("offset") int offset, @Param("limit") int limit);
}

5.4 NewsDAO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Mapper
public interface NewsDAO {
String TABLE_NAME = "news";
String INSERT_FIELDS = " title, link, image, like_count, comment_count, created_date, user_id ";
String SELECT_FIELDS = " id, " + INSERT_FIELDS;

@Insert({"insert into ", TABLE_NAME, "(", INSERT_FIELDS,
") values (#{title},#{link},#{image},#{likeCount},#{commentCount},#{createdDate},#{userId})"})
int addNews(News news);

@Select({"select ", SELECT_FIELDS , " from ", TABLE_NAME, " where id=#{id}"})
News getById(int id);

@Update({"update ", TABLE_NAME, " set comment_count = #{commentCount} where id=#{id}"})
int updateCommentCount(@Param("id") int id, @Param("commentCount") int commentCount);

@Update({"update ", TABLE_NAME, " set like_count = #{likeCount} where id=#{id}"})
int updateLikeCount(@Param("id") int id, @Param("likeCount") int likeCount);

List<News> selectByUserIdAndOffset(@Param("userId") int userId, @Param("offset") int offset,
@Param("limit") int limit);
}

5.5 UserDAO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Mapper
public interface UserDAO {
String TABLE_NAME = "user";
String INSET_FIELDS = " name, password, salt, head_url ";
String SELECT_FIELDS = " id, name, password, salt, head_url";

@Insert({"insert into ", TABLE_NAME, "(", INSET_FIELDS,
") values (#{name},#{password},#{salt},#{headUrl})"})
int addUser(User user);

@Select({"select ", SELECT_FIELDS, " from ", TABLE_NAME, " where id=#{id}"})
User selectById(int id);

@Select({"select ", SELECT_FIELDS, " from ", TABLE_NAME, " where name=#{name}"})
User selectByName(String name);

@Update({"update ", TABLE_NAME, " set password=#{password} where id=#{id}"})
void updatePassword(User user);

@Delete({"delete from ", TABLE_NAME, " where id=#{id}"})
void deleteById(int id);
}

6. interceptor

interceptor

6.1 LoginRequiredInterceptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Component
public class LoginRequiredInterceptor implements HandlerInterceptor {

@Autowired
private HostHolder hostHolder;

@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
if (hostHolder.getUser() == null) {
httpServletResponse.sendRedirect("/?pop=1");
return false;
}
return true;
}

@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}

@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}

1)拦截器(Interceptor)

作用

在Web开发中,拦截器(Interceptor)可以用来验证是否登录预先设置数据以及统计方法的执行效率等。

分类

Spring中的拦截器分两种,一是HandlerInterceptor,另一个是MethodInterceptor。这里主要说以下HandlerInterceptor。

HandlerInterceptor是SpringMVC项目中的拦截器,拦截目标是请求的地址,比MethodInterceptor先执行。实现一个HandlerInterceptor拦截器可以直接实现该接口,也可以继承HandlerInterceptorAdapter类。

SpringMVC处理请求过程

SpringMVC处理请求的整个过程是

  1. 先根据请求找到对应的HandlerExecutionChain,它包含了处理请求的handler和所有的HandlerInterceptor拦截器
  2. 然后在调用hander之前分别调用每个HandlerInterceptor拦截器preHandle方法
    2.1 若有一个拦截器返回false,则会调用triggerAfterCompletion方法,并且立即返回不再往下执行
    2.2 若所有的拦截器全部返回true并且没有出现异常,则调用handler返回ModelAndView对象;再然后分别调用每个拦截器的postHandle方法;最后,即使是之前的步骤抛出了异常,也会执行triggerAfterCompletion方法。

多拦截器工作流程:

多拦截器工作流程

需要Override的三种方法

(1 )preHandle (HttpServletRequest request, HttpServletResponse response, Object handle)
controller 执行之前调用
该方法将在请求处理之前进行调用。SpringMVC 中的Interceptor 是链式调用的,在一个请求中可以同时存在多个Interceptor 。每个Interceptor 的调用会依据它的声明顺序依次执行,而且最先执行的都是Interceptor 中的preHandle 方法,所以可以在这个方法中进行一些前置初始化操作或者是对当前请求的一个预处理,也可以在这个方法中进行一些判断来决定请求是否要继续进行下去。该方法的返回值是布尔值Boolean 类型的,当它返回为false 时,表示请求结束,后续的Interceptor 和Controller 都不会再执行;当返回值为true 时就会继续调用下一个Interceptor 的preHandle 方法,如果已经是最后一个Interceptor 的时候就会是调用当前请求的Controller 方法。
(2 )postHandle (HttpServletRequest request, HttpServletResponse response, Object handle, ModelAndView modelAndView)
controller 执行之后,且页面渲染之前调用
这个方法包括后面要说到的afterCompletion 方法都只能是在当前所属的Interceptor 的preHandle 方法的返回值为true 时才能被调用。postHandle 方法,是在当前请求进行处理之后,也就是Controller 方法调用之后执行,但是它会在DispatcherServlet 进行视图渲染之前被调用,所以我们可以在这个方法中对Controller 处理之后的ModelAndView 对象进行操作。postHandle 方法被调用的方向跟preHandle 是相反的,也就是说先声明的Interceptor 的postHandle 方法反而会后执行。
(3 )afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex)
页面渲染之后调用,一般用于资源清理操作。
该方法也是需要当前对应的Interceptor 的preHandle 方法的返回值为true 时才会执行。该方法将在整个请求结束之后,也就是在DispatcherServlet 渲染了对应的视图之后执行。这个方法的主要作用是用于进行资源清理工作的。

在Spring Boot中配置拦截器,需要写一个配置类继承WebMvcConfigurerAdapter类并添加该拦截器(见2)

1
2
3
4
5
6
7
8
9
10
11
@Component
public class XdtoutiaoWebConfiguration extends WebMvcConfigurerAdapter {
@Autowired
PassportInterceptor passportInterceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(passportInterceptor);
super.addInterceptors(registry);
}
}

6.2 PassportInterceptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@Component
public class PassportInterceptor implements HandlerInterceptor {

@Autowired
private LoginTicketDAO loginTicketDAO;

@Autowired
private UserDAO userDAO;

@Autowired
private HostHolder hostHolder;

@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
String ticket = null;
if (httpServletRequest.getCookies() != null) {
for (Cookie cookie : httpServletRequest.getCookies()) {
if (cookie.getName().equals("ticket")) {
ticket = cookie.getValue();
break;
}
}
}

if (ticket != null) {
LoginTicket loginTicket = loginTicketDAO.selectByTicket(ticket);
if (loginTicket == null || loginTicket.getExpired().before(new Date()) || loginTicket.getStatus() != 0) {
return true;
}

User user = userDAO.selectById(loginTicket.getUserId());
hostHolder.setUser(user);
}
return true;
}

@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
if (modelAndView != null && hostHolder.getUser() != null) {
modelAndView.addObject("user", hostHolder.getUser());
}
}

@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
hostHolder.clear();
}
}

7. model

model

7.1 Comment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public class Comment {
private int id;
private int userId;
private int entityId;
private int entityType;
private String content;
private Date createdDate;
private int status;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public int getUserId() {
return userId;
}

public void setUserId(int userId) {
this.userId = userId;
}

public int getEntityId() {
return entityId;
}

public void setEntityId(int entityId) {
this.entityId = entityId;
}

public int getEntityType() {
return entityType;
}

public void setEntityType(int entityType) {
this.entityType = entityType;
}

public String getContent() {
return content;
}

public void setContent(String content) {
this.content = content;
}

public Date getCreatedDate() {
return createdDate;
}

public void setCreatedDate(Date createdDate) {
this.createdDate = createdDate;
}

public int getStatus() {
return status;
}

public void setStatus(int status) {
this.status = status;
}
}

7.2 EntityType

1
2
3
4
public class EntityType {
public static int ENTITY_NEWS = 1;
public static int ENTITY_COMMENT = 2;
}

7.3 HostHolder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public class HostHolder {
private static ThreadLocal<User> users = new ThreadLocal<User>();

public User getUser() {
return users.get();
}
public void setUser(User user) {
users.set(user);
}

public void clear() {
users.remove();
}
}

7.4 LoginTicket

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class LoginTicket {
private int id;
private int userId;
private Date expired;
private int status;// 0有效,1无效
private String ticket;

public String getTicket() {
return ticket;
}
public void setTicket(String ticket) {
this.ticket = ticket;
}

public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}

public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}

public Date getExpired() {
return expired;
}
public void setExpired(Date expired) {
this.expired = expired;
}

public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
}

7.5 Message

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public class Message {
private int id;
private int fromId;
private int toId;
private String content;
private Date createdDate;
private int hasRead;
private String conversationId;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public int getFromId() {
return fromId;
}

public void setFromId(int fromId) {
this.fromId = fromId;
}

public int getToId() {
return toId;
}

public void setToId(int toId) {
this.toId = toId;
}

public String getContent() {
return content;
}

public void setContent(String content) {
this.content = content;
}

public Date getCreatedDate() {
return createdDate;
}

public void setCreatedDate(Date createdDate) {
this.createdDate = createdDate;
}

public int getHasRead() {
return hasRead;
}

public void setHasRead(int hasRead) {
this.hasRead = hasRead;
}

public String getConversationId() {
if (fromId < toId) {
return String.format("%d_%d", fromId, toId);
}
return String.format("%d_%d", toId, fromId);
}
}

7.6 News

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public class News {
private int id;
private String title;
private String link;
private String image;
private int likeCount;
private int commentCount;
private Date createdDate;
private int userId;

public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}

public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}

public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link;
}

public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}

public int getLikeCount() {
return likeCount;
}
public void setLikeCount(int likeCount) {
this.likeCount = likeCount;
}

public int getCommentCount() {
return commentCount;
}
public void setCommentCount(int commentCount) {
this.commentCount = commentCount;
}

public Date getCreatedDate() {
return createdDate;
}
public void setCreatedDate(Date createdDate) {
this.createdDate = createdDate;
}

public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
}

7.7 User

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class User {
private int id;
private String name;
private String password;
private String salt;
private String headUrl;

public User() {
}
public User(String name) {
this.name = name;
this.password = "";
this.salt = "";
this.headUrl = "";
}

public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}

public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}

public String getSalt() {
return salt;
}
public void setSalt(String salt) {
this.salt = salt;
}

public String getHeadUrl() {
return headUrl;
}
public void setHeadUrl(String headUrl) {
this.headUrl = headUrl;
}

public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}

7.8 ViewObject

1
2
3
4
5
6
7
8
9
10
public class ViewObject {
private Map<String, Object> objs = new HashMap<String, Object>();
public void set(String key, Object value) {
objs.put(key, value);
}

public Object get(String key) {
return objs.get(key);
}
}

8. service

service

8.1 CommentService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Service
public class CommentService {
@Autowired
private CommentDAO commentDAO;

public List<Comment> getCommentsByEntity(int entityId, int entityType) {
return commentDAO.selectByEntity(entityId, entityType);
}

public int addComment(Comment comment) {
return commentDAO.addComment(comment);
}

public int getCommentCount(int entityId, int entityType) {
return commentDAO.getCommentCount(entityId, entityType);
}

public void deleteComment(int entityId, int entityType) {
commentDAO.updateStatus(entityId, entityType, 1);
}
}

8.2 LikeService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Service
public class LikeService {
@Autowired
JedisAdapter jedisAdapter;

public int getLikeStatus(int userId, int entityType, int entityId) {
String likeKey = RedisKeyUtil.getLikeKey(entityId, entityType);
if(jedisAdapter.sismember(likeKey, String.valueOf(userId))) {
return 1;
}
String disLikeKey = RedisKeyUtil.getDisLikeKey(entityId, entityType);
return jedisAdapter.sismember(disLikeKey, String.valueOf(userId)) ? -1 : 0;
}

public long like(int userId, int entityType, int entityId) {
// 在喜欢集合里增加
String likeKey = RedisKeyUtil.getLikeKey(entityId, entityType);
jedisAdapter.sadd(likeKey, String.valueOf(userId));
// 从反对里删除
String disLikeKey = RedisKeyUtil.getDisLikeKey(entityId, entityType);
jedisAdapter.srem(disLikeKey, String.valueOf(userId));
return jedisAdapter.scard(likeKey);
}

public long disLike(int userId, int entityType, int entityId) {
// 在反对集合里增加
String disLikeKey = RedisKeyUtil.getDisLikeKey(entityId, entityType);
jedisAdapter.sadd(disLikeKey, String.valueOf(userId));
// 从喜欢里删除
String likeKey = RedisKeyUtil.getLikeKey(entityId, entityType);
jedisAdapter.srem(likeKey, String.valueOf(userId));
return jedisAdapter.scard(likeKey);
}
}

8.3 MessageService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MessageService {
@Autowired
MessageDAO messageDAO;
public int addMessage(Message message) {
return messageDAO.addMessage(message);
}

public List<Message> getConversationDetail(String conversationId, int offset, int limit) {
return messageDAO.getConversationDetail(conversationId, offset, limit);
}

public List<Message> getConversationList(int userId, int offset, int limit) {
return messageDAO.getConversationList(userId, offset, limit);
}

public int getConvesationUnreadCount(int userId, String conversationId) {
return messageDAO.getConvesationUnreadCount(userId, conversationId);
}
}

8.4 NewsService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Service
public class NewsService {
@Autowired
private NewsDAO newsDAO;

public List<News> getLatestNews(int userId, int offset, int limit) {
return newsDAO.selectByUserIdAndOffset(userId, offset, limit);
}

public int addNews(News news) {
newsDAO.addNews(news);
return news.getId();
}

public News getById(int newsId) {
return newsDAO.getById(newsId);
}

public String saveImage(MultipartFile file) throws IOException {
int dotPos = file.getOriginalFilename().lastIndexOf(".");
if (dotPos < 0) {
return null;
}
String fileExt = file.getOriginalFilename().substring(dotPos + 1).toLowerCase();
if (!ToutiaoUtil.isFileAllowed(fileExt)) {
return null;
}

String fileName = UUID.randomUUID().toString().replaceAll("-", "") + "." + fileExt;
Files.copy(file.getInputStream(), new File(ToutiaoUtil.IMAGE_DIR + fileName).toPath(),
StandardCopyOption.REPLACE_EXISTING);
return ToutiaoUtil.TOUTIAO_DOMAIN + "image?name=" + fileName;
}

public int updateCommentCount(int id, int count) {
return newsDAO.updateCommentCount(id, count);
}

public int updateLikeCount(int id, int count) {
return newsDAO.updateLikeCount(id, count);
}
}

8.5 QiniuService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@Service
public class QiniuService {
private static final Logger logger = LoggerFactory.getLogger(QiniuService.class);
//设置好账号的ACCESS_KEY和SECRET_KEY
String ACCESS_KEY = "abNXnXBIlI6viRaOeRY6Hk-zc3V-NpjLcGfYz5kD";
String SECRET_KEY = "QP7Xja3FmP1Zyl-oxwQDCb7T6wCoEFKoO-0vht_5";
//要上传的空间
String bucketname = "nowcoder";

//密钥配置
Auth auth = Auth.create(ACCESS_KEY, SECRET_KEY);
//创建上传对象
UploadManager uploadManager = new UploadManager();

private static String QINIU_IMAGE_DOMAIN = "http://7xsetu.com1.z0.glb.clouddn.com/";

//简单上传,使用默认策略,只需要设置上传的空间名就可以了
public String getUpToken() {
return auth.uploadToken(bucketname);
}

public String saveImage(MultipartFile file) throws IOException {
try {
int dotPos = file.getOriginalFilename().lastIndexOf(".");
if (dotPos < 0) {
return null;
}
String fileExt = file.getOriginalFilename().substring(dotPos + 1).toLowerCase();
if (!ToutiaoUtil.isFileAllowed(fileExt)) {
return null;
}

String fileName = UUID.randomUUID().toString().replaceAll("-", "") + "." + fileExt;
//调用put方法上传
Response res = uploadManager.put(file.getBytes(), fileName, getUpToken());
//打印返回的信息
if (res.isOK() && res.isJson()) {
return QINIU_IMAGE_DOMAIN + JSONObject.parseObject(res.bodyString()).get("key");
} else {
logger.error("七牛异常:" + res.bodyString());
return null;
}
} catch (QiniuException e) {
// 请求失败时打印的异常的信息
logger.error("七牛异常:" + e.getMessage());
return null;
}
}
}

8.6 ToutiaoService

1
2
3
4
5
6
@Service
public class ToutiaoService {
public String say() {
return "This is from ToutiaoService";
}
}

8.7 UserService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
@Service
public class UserService {
private static final Logger logger = LoggerFactory.getLogger(UserService.class);
@Autowired
private UserDAO userDAO;

@Autowired
private LoginTicketDAO loginTicketDAO;

public Map<String, Object> register(String username, String password) {
Map<String, Object> map = new HashMap<String, Object>();
if (StringUtils.isBlank(username)) {
map.put("msgname", "用户名不能为空");
return map;
}

if (StringUtils.isBlank(password)) {
map.put("msgpwd", "密码不能为空");
return map;
}

User user = userDAO.selectByName(username);

if (user != null) {
map.put("msgname", "用户名已经被注册");
return map;
}

// 密码强度
user = new User();
user.setName(username);
user.setSalt(UUID.randomUUID().toString().substring(0, 5));
String head = String.format("http://images.nowcoder.com/head/%dt.png", new Random().nextInt(1000));
user.setHeadUrl(head);
user.setPassword(ToutiaoUtil.MD5(password+user.getSalt()));
userDAO.addUser(user);

// 登陆
String ticket = addLoginTicket(user.getId());
map.put("ticket", ticket);
return map;
}


public Map<String, Object> login(String username, String password) {
Map<String, Object> map = new HashMap<String, Object>();
if (StringUtils.isBlank(username)) {
map.put("msgname", "用户名不能为空");
return map;
}

if (StringUtils.isBlank(password)) {
map.put("msgpwd", "密码不能为空");
return map;
}

User user = userDAO.selectByName(username);

if (user == null) {
map.put("msgname", "用户名不存在");
return map;
}

if (!ToutiaoUtil.MD5(password+user.getSalt()).equals(user.getPassword())) {
map.put("msgpwd", "密码不正确");
return map;
}

map.put("userId", user.getId());

String ticket = addLoginTicket(user.getId());
map.put("ticket", ticket);
return map;
}

private String addLoginTicket(int userId) {
LoginTicket ticket = new LoginTicket();
ticket.setUserId(userId);
Date date = new Date();
date.setTime(date.getTime() + 1000*3600*24);
ticket.setExpired(date);
ticket.setStatus(0);
ticket.setTicket(UUID.randomUUID().toString().replaceAll("-", ""));
loginTicketDAO.addTicket(ticket);
return ticket.getTicket();
}

public User getUser(int id) {
return userDAO.selectById(id);
}

public void logout(String ticket) {
loginTicketDAO.updateStatus(ticket, 1);
}
}

9. util

util

9.1 JedisAdapter

利用JSON进行对象序列化和反序列化,JSON存到Redis中,可以做在在Redis中存储对象。

1
2
3
4
5
6
7
8
9
10
11
12
//将一个对象转换为一个jsoon串存入set中
public void setObject(String key, Object obj) {
set(key, JSON.toJSONString(obj));
}
//从set中取出json串,并转换为相应object
public <T> T getObject(String key, Class<T> clazz) {
String value = get(key);
if (value != null) {
return JSON.parseObject(value, clazz);
}
return null;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void testObject() {
User user = new User();
user.setHeadUrl("http://images.nowcoder.com/head/100t.png");
user.setName("user1");
user.setPassword("abc");
user.setSalt("def");
jedisAdapter.setObject("user1", user);

User u = jedisAdapter.getObject("user1", User.class);
System.out.print(ToStringBuilder.reflectionToString(u)); //反序列化

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
@Service
public class JedisAdapter implements InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(JedisAdapter.class);

public static void print(int index, Object obj) {
System.out.println(String.format("%d,%s", index, obj.toString()));
}

public static void mainx(String[] args) {
Jedis jedis = new Jedis();
jedis.flushAll();
// get,set
jedis.set("hello", "world");
print(1, jedis.get("hello"));
jedis.rename("hello", "newhello");
print(1, jedis.get("newhello"));
jedis.setex("hello2", 15, "world");

// 数值操作
jedis.set("pv", "100");
jedis.incr("pv");
jedis.decrBy("pv", 5);
print(2, jedis.get("pv"));
print(3, jedis.keys("*"));

// 列表操作, 最近来访, 粉丝列表,消息队列
String listName = "list";
jedis.del(listName);
for (int i = 0; i < 10; ++i) {
jedis.lpush(listName, "a" + String.valueOf(i));
}
print(4, jedis.lrange(listName, 0, 12)); // 最近来访10个id
print(5, jedis.llen(listName));
print(6, jedis.lpop(listName));
print(7, jedis.llen(listName));
print(8, jedis.lrange(listName, 2, 6)); // 最近来访10个id
print(9, jedis.lindex(listName, 3));
print(10, jedis.linsert(listName, BinaryClient.LIST_POSITION.AFTER, "a4", "xx"));
print(10, jedis.linsert(listName, BinaryClient.LIST_POSITION.BEFORE, "a4", "bb"));
print(11, jedis.lrange(listName, 0, 12));


// hash, 可变字段
String userKey = "userxx";
jedis.hset(userKey, "name", "jim");
jedis.hset(userKey, "age", "12");
jedis.hset(userKey, "phone", "18666666666");
print(12, jedis.hget(userKey, "name"));
print(13, jedis.hgetAll(userKey));
jedis.hdel(userKey, "phone");
print(14, jedis.hgetAll(userKey));
print(15, jedis.hexists(userKey, "email"));
print(16, jedis.hexists(userKey, "age"));
print(17, jedis.hkeys(userKey));
print(18, jedis.hvals(userKey));
jedis.hsetnx(userKey, "school", "zju");
jedis.hsetnx(userKey, "name", "yxy");
print(19, jedis.hgetAll(userKey));

// 集合,点赞用户群, 共同好友
String likeKey1 = "newsLike1";
String likeKey2 = "newsLike2";
for (int i = 0; i < 10; ++i) {
jedis.sadd(likeKey1, String.valueOf(i));
jedis.sadd(likeKey2, String.valueOf(i * 2));
}
print(20, jedis.smembers(likeKey1));
print(21, jedis.smembers(likeKey2));
print(22, jedis.sunion(likeKey1, likeKey2));
print(23, jedis.sdiff(likeKey1, likeKey2));
print(24, jedis.sinter(likeKey1, likeKey2));
print(25, jedis.sismember(likeKey1, "12"));
print(26, jedis.sismember(likeKey2, "12"));
jedis.srem(likeKey1, "5");
print(27, jedis.smembers(likeKey1));
// 从1移动到2
jedis.smove(likeKey2, likeKey1, "14");
print(28, jedis.smembers(likeKey1));
print(29, jedis.scard(likeKey1));

// 排序集合,有限队列,排行榜
String rankKey = "rankKey";
jedis.zadd(rankKey, 15, "Jim");
jedis.zadd(rankKey, 60, "Ben");
jedis.zadd(rankKey, 90, "Lee");
jedis.zadd(rankKey, 75, "Lucy");
jedis.zadd(rankKey, 80, "Mei");
print(30, jedis.zcard(rankKey));
print(31, jedis.zcount(rankKey, 61, 100));
// 改错卷了
print(32, jedis.zscore(rankKey, "Lucy"));
jedis.zincrby(rankKey, 2, "Lucy");
print(33, jedis.zscore(rankKey, "Lucy"));
jedis.zincrby(rankKey, 2, "Luc");
print(34, jedis.zscore(rankKey, "Luc"));
print(35, jedis.zcount(rankKey, 0, 100));
// 1-4 名 Luc
print(36, jedis.zrange(rankKey, 0, 10));
print(36, jedis.zrange(rankKey, 1, 3));
print(36, jedis.zrevrange(rankKey, 1, 3));
for (Tuple tuple : jedis.zrangeByScoreWithScores(rankKey, "60", "100")) {
print(37, tuple.getElement() + ":" + String.valueOf(tuple.getScore()));
}

print(38, jedis.zrank(rankKey, "Ben"));
print(39, jedis.zrevrank(rankKey, "Ben"));

String setKey = "zset";
jedis.zadd(setKey, 1, "a");
jedis.zadd(setKey, 1, "b");
jedis.zadd(setKey, 1, "c");
jedis.zadd(setKey, 1, "d");
jedis.zadd(setKey, 1, "e");
print(40, jedis.zlexcount(setKey, "-", "+"));
print(41, jedis.zlexcount(setKey, "(b", "[d"));
print(42, jedis.zlexcount(setKey, "[b", "[d"));
jedis.zrem(setKey, "b");
print(43, jedis.zrange(setKey, 0, 10));
jedis.zremrangeByLex(setKey, "(c", "+");
print(44, jedis.zrange(setKey, 0, 2));

/*
jedis.lpush("aaa", "A");
jedis.lpush("aaa", "B");
jedis.lpush("aaa", "C");
print(45, jedis.brpop(0, "aaa"));
print(45, jedis.brpop(0, "aaa"));
print(45, jedis.brpop(0, "aaa"));
*/
JedisPool pool = new JedisPool();
for (int i = 0; i < 100; ++i) {
Jedis j = pool.getResource();
j.get("a");
j.close();
}
}

private Jedis jedis = null;
private JedisPool pool = null;

@Override
public void afterPropertiesSet() throws Exception {
//jedis = new Jedis("localhost");
pool = new JedisPool("localhost", 6379);
}

public String get(String key) {
Jedis jedis = null;
try {
jedis = pool.getResource();
return jedis.get(key);
} catch (Exception e) {
logger.error("发生异常" + e.getMessage());
return null;
} finally {
if (jedis != null) {
jedis.close();
}
}
}

public void set(String key, String value) {
Jedis jedis = null;
try {
jedis = pool.getResource();
jedis.set(key, value);
} catch (Exception e) {
logger.error("发生异常" + e.getMessage());
} finally {
if (jedis != null) {
jedis.close();
}
}
}

public long sadd(String key, String value) {
Jedis jedis = null;
try {
jedis = pool.getResource();
return jedis.sadd(key, value);
} catch (Exception e) {
logger.error("发生异常" + e.getMessage());
return 0;
} finally {
if (jedis != null) {
jedis.close();
}
}
}

public long srem(String key, String value) {
Jedis jedis = null;
try {
jedis = pool.getResource();
return jedis.srem(key, value);
} catch (Exception e) {
logger.error("发生异常" + e.getMessage());
return 0;
} finally {
if (jedis != null) {
jedis.close();
}
}
}

public boolean sismember(String key, String value) {
Jedis jedis = null;
try {
jedis = pool.getResource();
return jedis.sismember(key, value);
} catch (Exception e) {
logger.error("发生异常" + e.getMessage());
return false;
} finally {
if (jedis != null) {
jedis.close();
}
}
}

public long scard(String key) {
Jedis jedis = null;
try {
jedis = pool.getResource();
return jedis.scard(key);
} catch (Exception e) {
logger.error("发生异常" + e.getMessage());
return 0;
} finally {
if (jedis != null) {
jedis.close();
}
}
}

public void setex(String key, String value) {
// 验证码, 防机器注册,记录上次注册时间,有效期3天
Jedis jedis = null;
try {
jedis = pool.getResource();
jedis.setex(key, 10, value);
} catch (Exception e) {
logger.error("发生异常" + e.getMessage());
} finally {
if (jedis != null) {
jedis.close();
}
}
}

public long lpush(String key, String value) {
Jedis jedis = null;
try {
jedis = pool.getResource();
return jedis.lpush(key, value);
} catch (Exception e) {
logger.error("发生异常" + e.getMessage());
return 0;
} finally {
if (jedis != null) {
jedis.close();
}
}
}

public List<String> brpop(int timeout, String key) {
Jedis jedis = null;
try {
jedis = pool.getResource();
return jedis.brpop(timeout, key);
} catch (Exception e) {
logger.error("发生异常" + e.getMessage());
return null;
} finally {
if (jedis != null) {
jedis.close();
}
}
}

public void setObject(String key, Object obj) {
set(key, JSON.toJSONString(obj));
}

public <T> T getObject(String key, Class<T> clazz) {
String value = get(key);
if (value != null) {
return JSON.parseObject(value, clazz);
}
return null;
}

}

9.2 MailSender

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@Service
public class MailSender implements InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(MailSender.class);
private JavaMailSenderImpl mailSender;

@Autowired
private VelocityEngine velocityEngine;

public boolean sendWithHTMLTemplate(String to, String subject,
String template, Map<String, Object> model) {
try {
String nick = MimeUtility.encodeText("牛客中级课");
InternetAddress from = new InternetAddress(nick + "<course@nowcoder.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 (Exception e) {
logger.error("发送邮件失败" + e.getMessage());
return false;
}
}

@Override
public void afterPropertiesSet() throws Exception {
mailSender = new JavaMailSenderImpl();
mailSender.setUsername("course@nowcoder.com");
mailSender.setPassword("NKnk66");
mailSender.setHost("smtp.exmail.qq.com");
mailSender.setPort(465);
mailSender.setProtocol("smtps");
mailSender.setDefaultEncoding("utf8");
Properties javaMailProperties = new Properties();
javaMailProperties.put("mail.smtp.ssl.enable", true);
mailSender.setJavaMailProperties(javaMailProperties);
}
}

9.3 RedisKeyUtil

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class RedisKeyUtil {
private static String SPLIT = ":";
private static String BIZ_LIKE = "LIKE";
private static String BIZ_DISLIKE = "DISLIKE";
private static String BIZ_EVENT = "EVENT";

public static String getEventQueueKey() {
return BIZ_EVENT;
}

public static String getLikeKey(int entityId, int entityType) {
return BIZ_LIKE + SPLIT + String.valueOf(entityType) + SPLIT + String.valueOf(entityId);
}

public static String getDisLikeKey(int entityId, int entityType) {
return BIZ_DISLIKE + SPLIT + String.valueOf(entityType) + SPLIT + String.valueOf(entityId);
}
}

9.4 ToutiaoUtil

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public class ToutiaoUtil {
private static final Logger logger = LoggerFactory.getLogger(ToutiaoUtil.class);

public static String TOUTIAO_DOMAIN = "http://127.0.0.1:8080/";
public static String IMAGE_DIR = "D:/upload/";
public static String[] IMAGE_FILE_EXTD = new String[] {"png", "bmp", "jpg", "jpeg"};

public static boolean isFileAllowed(String fileName) {
for (String ext : IMAGE_FILE_EXTD) {
if (ext.equals(fileName)) {
return true;
}
}
return false;
}
public static String getJSONString(int code) {
JSONObject json = new JSONObject();
json.put("code", code);
return json.toJSONString();
}

public static String getJSONString(int code, String msg) {
JSONObject json = new JSONObject();
json.put("code", code);
json.put("msg", msg);
return json.toJSONString();
}

public static String getJSONString(int code, Map<String, Object> map) {
JSONObject json = new JSONObject();
json.put("code", code);
for (Map.Entry<String, Object> entry : map.entrySet()) {
json.put(entry.getKey(), entry.getValue());
}
return json.toJSONString();
}

public static String MD5(String key) {
char hexDigits[] = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};
try {
byte[] btInput = key.getBytes();
// 获得MD5摘要算法的 MessageDigest 对象
MessageDigest mdInst = MessageDigest.getInstance("MD5");
// 使用指定的字节更新摘要
mdInst.update(btInput);
// 获得密文
byte[] md = mdInst.digest();
// 把密文转换成十六进制的字符串形式
int j = md.length;
char str[] = new char[j * 2];
int k = 0;
for (int i = 0; i < j; i++) {
byte byte0 = md[i];
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
str[k++] = hexDigits[byte0 & 0xf];
}
return new String(str);
} catch (Exception e) {
logger.error("生成MD5失败", e);
return null;
}
}
}

10. ToutiaoApplication

1
2
3
4
5
6
7
8
9
10
11
@SpringBootApplication
public class ToutiaoApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(ToutiaoApplication.class);
}

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

spring 的SpringApplication

作用:入口类

Spring应用的入口类是Spring应用的配置起点,是配置Spring上下文的起点,往往使用了@SpringBootApplication或@EnableAutoConfiguration等标注类。

在Spring应用的入口类中往往只有一个main()方法,这虽然与标准的Java应用保持了一致,但在有些时候会让开发人员觉得困惑。

在Spring应用的入口类中的main()方法中,往往只是简单地调用Spring Boot的SpringApplication类的run()方法,以启动该Spring应用。

1
SpringApplication.run(MySpringConfigurationApp.class, args);

Spring Boot的SpringApplication类

Spring Boot的SpringApplication类,用以启动一个Spring应用,实质上是为Spring应用创建并初始化Spring上下文

SpringApplication类的run()方法默认返回一个ConfigurableApplicationContext对象。

11. 配置文件及模板

配置文件及模板

11.1 NewsDAO.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.nowcoder.dao.NewsDAO">
<sql id="table">news</sql>
<sql id="selectFields">id,title, link, image, like_count, comment_count,created_date,user_id
</sql>
<select id="selectByUserIdAndOffset" resultType="com.nowcoder.model.News">
SELECT
<include refid="selectFields"/>
FROM
<include refid="table"/>

<if test="userId != 0">
WHERE user_id = #{userId}
</if>
ORDER BY id DESC
LIMIT #{offset},#{limit}
</select>
</mapper>

MyBatis的Mapper XML 文件

MyBatis 的真正强大在于它的映射语句,也是它的魔力所在。

详细请见官方文档

SQL 映射顶级元素(按照它们应该被定义的顺序)

cache – 给定命名空间的缓存配置
cache-ref – 其他命名空间缓存配置的引用
resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象
sql – 可被其他语句引用的可重用语句块
insert – 映射插入语句
update – 映射更新语句
delete – 映射删除语句
select – 映射查询语句

属性

属性 描述
id 在命名空间中唯一的标识符,可以被用来引用这条语句
parameterType 将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,默认值为 unset
parameterMap 这是引用外部 parameterMap 的已经被废弃的方法。使用内联参数映射和 parameterType 属性
resultType 从这条语句中返回的期望类型的类的完全限定名或别名。注意如果是集合情形,那应该是集合可以包含的类型,而不能是集合本身。使用 resultType 或 resultMap,但不能同时使用

11.2 application.properties

1
2
3
4
5
6
7
8
spring.datasource.url=jdbc:mysql://localhost:3306/toutiao?useUnicode=true&characterEncoding=utf8&useSSL=false
spring.datasource.username=root
spring.datasource.password=nowcoder
mybatis.config-location=classpath:mybatis-config.xml
#logging.level.root=DEBUG
spring.velocity.suffix=.html
spring.velocity.cache=false
spring.velocity.toolbox-config-location=toolbox.xml

11.3 mybatis-config.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

<settings>
<!-- Globally enables or disables any caches configured in any mapper under this configuration -->
<setting name="cacheEnabled" value="true"/>
<!-- Sets the number of seconds the driver will wait for a response from the database -->
<setting name="defaultStatementTimeout" value="3000"/>
<!-- Enables automatic mapping from classic database column names A_COLUMN to camel case classic Java property names aColumn -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- Allows JDBC support for generated keys. A compatible driver is required.
This setting forces generated keys to be used if set to true,
as some drivers deny compatibility but still work -->
<setting name="useGeneratedKeys" value="true"/>
</settings>
<!-- Continue going here -->
</configuration>

11.4 toolbox.xml

1
2
3
4
5
6
7
<toolbox>
<tool>
<key>date</key>
<scope>application</scope>
<class>org.apache.velocity.tools.generic.DateTool</class>
</tool>
</toolbox>
  1. velocity-tools 提供了很多实用的 Java 类,使用这些小工具前,需要在 web.xml 中配置 toolbox.xml 文件,在 VelocityViewServlet 后加入另一个参数:
1
2
3
4
<init-param>
<param-name>org.apache.velocity.toolbox</param-name>
<param-value>/WEB-INF/toolbox.xml</param-value>
</init-param>

这个参数指定了 toolbox.xml 的位置,通常我们把配置文件放在 WEB-INF 下。

12. test

test

12.1 InitDatabaseTests

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = ToutiaoApplication.class)
@Sql("/init-schema.sql")
public class InitDatabaseTests {
@Autowired
UserDAO userDAO;

@Autowired
NewsDAO newsDAO;

@Autowired
LoginTicketDAO loginTicketDAO;

@Autowired
CommentDAO commentDAO;

@Test
public void initData() {
Random random = new Random();
for (int i = 0; i < 11; ++i) {
User user = new User();
user.setHeadUrl(String.format("http://images.nowcoder.com/head/%dt.png", random.nextInt(1000)));
user.setName(String.format("USER%d", i));
user.setPassword("");
user.setSalt("");
userDAO.addUser(user);

News news = new News();
news.setCommentCount(i);
Date date = new Date();
date.setTime(date.getTime() + 1000*3600*5*i);
news.setCreatedDate(date);
news.setImage(String.format("http://images.nowcoder.com/head/%dm.png", random.nextInt(1000)));
news.setLikeCount(i+1);
news.setUserId(i+1);
news.setTitle(String.format("TITLE{%d}", i));
news.setLink(String.format("http://www.nowcoder.com/%d.html", i));
newsDAO.addNews(news);

for (int j = 0; j < 3; ++j) {
Comment comment = new Comment();
comment.setUserId(i+1);
comment.setEntityId(news.getId());
comment.setEntityType(EntityType.ENTITY_NEWS);
comment.setStatus(0);
comment.setCreatedDate(new Date());
comment.setContent("Comment " + String.valueOf(j));
commentDAO.addComment(comment);
}

user.setPassword("newpassword");
userDAO.updatePassword(user);

LoginTicket ticket = new LoginTicket();
ticket.setStatus(0);
ticket.setUserId(i+1);
ticket.setExpired(date);
ticket.setTicket(String.format("TICKET%d", i+1));
loginTicketDAO.addTicket(ticket);

loginTicketDAO.updateStatus(ticket.getTicket(), 2);

}

Assert.assertEquals("newpassword", userDAO.selectById(1).getPassword());
userDAO.deleteById(1);
Assert.assertNull(userDAO.selectById(1));

Assert.assertEquals(1, loginTicketDAO.selectByTicket("TICKET1").getUserId());
Assert.assertEquals(2, loginTicketDAO.selectByTicket("TICKET1").getStatus());

Assert.assertNotNull(commentDAO.selectByEntity(1, EntityType.ENTITY_NEWS).get(0));
}
}

1)SpringBoot Web项目中中如何使用Junit

详见Spring Boot Junit单元测试

  1. 创建一个普通的Java类,在Junit4中不再需要继承TestCase类了
  2. 因为我们是Web项目,所以在创建的Java类中添加注解:
注解 作用
@RunWith(SpringJUnit4ClassRunner.class) SpringJUnit支持,由此引入Spring-Test框架支持
@SpringApplicationConfiguration(classes = SpringBootSampleApplication.class) 指定我们SpringBoot工程的Application启动类
@WebAppConfiguration 由于是Web项目,Junit需要模拟ServletContext,因此我们需要给我们的测试类加上@WebAppConfiguration
  1. 接下来就可以编写测试方法了,测试方法使用@Test注解标注即可。

2)随机数—random.nextInt(1000)

java中一般有两种随机数,一个是Math中random()方法,一个是Random类。

一、Math.random()

调用Math.Random()函数能够返回带正号的double值,该值大于等于0.0且小于1.0,即取值范围是[0.0,1.0)的左闭右开区间

二、Random类

在进行随机时,随机算法的起源数字称为种子数(seed),在种子数的基础上进行一定的变换,从而产生需要的随机数字。

相同种子数的Random对象,相同次数生成的随机数字是完全相同的。也就是说,两个种子数相同的Random对象,第一次生成的随机数字完全相同,第二次生成的随机数字也完全相同。
|Random类中的构造方法|作用|
|—|—|
|Random random = new Random();|默认构造方法|
|Random random = new Random(1000);|指定种子数字|

Random类中各方法生成的随机数字都是均匀分布的,也就是说区间内部的数字生成的几率是均等的。

Random类中的常用方法 作用
public boolean nextBoolean() 该方法的作用是生成一个随机的boolean值,生成true和false的值几率相等,也就是都是50%的几率
public double nextDouble() 该方法的作用是生成一个随机的double值,数值介于[0,1.0)之间,这里中括号代表包含区间端点,小括号代表不包含区间端点,也就是0到1之间的随机小数,包含0而不包含1.0
public int nextInt() 该方法的作用是生成一个随机的int值,该值介于int的区间,也就是-2的31次方到2的31次方-1之间
public int nextInt(int n) 该方法的作用是生成一个随机的int值,该值介于[0,n)的区间,也就是0到n之间的随机int值,包含0而不包含n
public void setSeed(long seed) 该方法的作用是重新设置Random对象中的种子数。设置完种子数以后的Random对象和相同种子数使用new关键字创建出的Random对象相同

12.2 JedisTests

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = ToutiaoApplication.class)
public class JedisTests {
@Autowired
JedisAdapter jedisAdapter;

@Test
public void testObject() {
User user = new User();
user.setHeadUrl("http://image.nowcoder.com/head/100t.png");
user.setName("user1");
user.setPassword("pwd");;
user.setSalt("salt");

jedisAdapter.setObject("user1xx", user);

User u = jedisAdapter.getObject("user1xx", User.class);

System.out.println(ToStringBuilder.reflectionToString(u));
}
}

12.3 LikeServiceTests

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = ToutiaoApplication.class)
public class LikeServiceTests {
@Autowired
LikeService likeService;

@Test
public void testLike() {
likeService.like(123, 1, 1);
Assert.assertEquals(1, likeService.getLikeStatus(123, 1, 1));

likeService.disLike(123, 1, 1);
Assert.assertEquals(-1, likeService.getLikeStatus(123, 1, 1));
}

@Test
public void testB() {
System.out.println("B");
}

@Test(expected = IllegalArgumentException.class)
public void testException() {
throw new IllegalArgumentException("异常");
}

@Before
public void setUp() {
System.out.println("setUp");
}

@After
public void tearDown() {
System.out.println("tearDown");
}

@BeforeClass
public static void beforeClass() {
System.out.println("beforeClass");
}

@AfterClass
public static void afterClass() {
System.out.println("afterClass");
}
}

12.4 测试多线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
class MyThread extends Thread {
private int tid;
public MyThread(int tid) {
this.tid = tid;
}

@Override
public void run() {
try {
for (int i = 0; i < 10; ++i) {
Thread.sleep(1000);
System.out.println(String.format("T%d:%d", tid, i));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

class Producer implements Runnable {
private BlockingQueue<String> q;

public Producer(BlockingQueue<String> q) {
this.q = q;
}
@Override
public void run() {
try {
for (int i = 0; i < 100; ++i) {
Thread.sleep(10);
q.put(String.valueOf(i));
}

} catch (Exception e) {
e.printStackTrace();
}
}
}

class Consumer implements Runnable {
private BlockingQueue<String> q;

public Consumer(BlockingQueue<String> q) {
this.q = q;
}
@Override
public void run() {
try {
while (true) {
System.out.println(Thread.currentThread().getName() + ":" + q.take());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

public class MultiThread {
public static void testThread() {
for (int i = 0; i < 10; ++i) {
//new MyThread(i).start();
}

for (int i = 0; i < 10; ++i) {
final int tid = i;
new Thread(new Runnable() {
@Override
public void run() {
try {
for (int i = 0; i < 10; ++i) {
Thread.sleep(1000);
System.out.println(String.format("T2%d:%d", tid, i));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}

private static Object obj = new Object();

public static void testSynchronized1() {
synchronized (obj) {
try {
for (int i = 0; i < 10; ++i) {
Thread.sleep(1000);
System.out.println(String.format("T3%d", i));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

public static void testSynchronized2() {
synchronized (new Object()) {
try {
for (int i = 0; i < 10; ++i) {
Thread.sleep(1000);
System.out.println(String.format("T4%d", i));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

public static void testSynchronized() {
for (int i = 0; i < 10; ++i) {
new Thread(new Runnable() {
@Override
public void run() {
testSynchronized1();
testSynchronized2();
}
}).start();
}
}

public static void testBlockingQueue() {
BlockingQueue<String> q = new ArrayBlockingQueue<String>(10);
new Thread(new Producer(q)).start();
new Thread(new Consumer(q), "Consumer1").start();
new Thread(new Consumer(q), "Consumer2").start();
}

private static int counter = 0;
private static AtomicInteger atomicInteger = new AtomicInteger(0);
public static void sleep(int mills) {
try {
//Thread.sleep(new Random().nextInt(mills));
Thread.sleep(mills);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void testWithAtomic() {
for (int i = 0; i < 10; ++i) {
new Thread(new Runnable() {
@Override
public void run() {
sleep(1000);
for (int j = 0; j < 10; ++j) {
System.out.println(atomicInteger.incrementAndGet());
}
}
}).start();
}
}

public static void testWithoutAtomic() {
for (int i = 0; i < 10; ++i) {
new Thread(new Runnable() {
@Override
public void run() {
sleep(1000);
for (int j = 0; j < 10; ++j) {
counter++;
System.out.println(counter);
}
}
}).start();
}
}

public static void testAtomic() {
testWithAtomic();
testWithoutAtomic();
}

private static ThreadLocal<Integer> threadLocalUserIds = new ThreadLocal<>();
private static int userId;

public static void testThreadLocal() {
for (int i = 0; i < 10; ++i) {
final int finalI = i;
new Thread(new Runnable() {
@Override
public void run() {
threadLocalUserIds.set(finalI);
sleep(1000);
System.out.println("ThreadLocal: " + threadLocalUserIds.get());
}
}).start();
}

for (int i = 0; i < 10; ++i) {
final int finalI = i;
new Thread(new Runnable() {
@Override
public void run() {
userId = finalI;
sleep(1000);
System.out.println("NonThreadLocal: " + userId);
}
}).start();
}
}

public static void testExecutor() {
//ExecutorService service = Executors.newSingleThreadExecutor();
ExecutorService service = Executors.newFixedThreadPool(2);
service.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; ++i) {
sleep(1000);
System.out.println("Execute1 " + i);
}
}
});

service.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; ++i) {
sleep(1000);
System.out.println("Execute2 " + i);
}
}
});

service.shutdown();
while (!service.isTerminated()) {
sleep(1000);
System.out.println("Wait for termination.");
}
}

public static void testFutrue() {
ExecutorService service = Executors.newSingleThreadExecutor();
Future<Integer> future = service.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
sleep(1000);
return 1;
//throw new IllegalArgumentException("异常");
}
});

service.shutdown();

try {
//System.out.println(future.get());
System.out.println(future.get(100, TimeUnit.MILLISECONDS));
} catch (Exception e) {
e.printStackTrace();
}
}


public static void main(String[] argv) {
//testThread();
//testSynchronized();
//testBlockingQueue();
//testAtomic();
//testThreadLocal();
//testExecutor();
testFutrue();
}
}

12.5 ToutiaoApplicationTests

1
2
3
4
5
6
7
8
9
10
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = ToutiaoApplication.class)
@WebAppConfiguration
public class ToutiaoApplicationTests {

@Test
public void contextLoads() {
}

}

12.6 init-schama.sql

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(64) NOT NULL DEFAULT '',
`password` varchar(128) NOT NULL DEFAULT '',
`salt` varchar(32) NOT NULL DEFAULT '',
`head_url` varchar(256) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `news`;
CREATE TABLE `news` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(128) NOT NULL DEFAULT '',
`link` varchar(256) NOT NULL DEFAULT '',
`image` varchar(256) NOT NULL DEFAULT '',
`like_count` int NOT NULL,
`comment_count` int NOT NULL,
`created_date` datetime NOT NULL,
`user_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `login_ticket`;
CREATE TABLE `login_ticket` (
`id` INT NOT NULL AUTO_INCREMENT,
`user_id` INT NOT NULL,
`ticket` VARCHAR(45) NOT NULL,
`expired` DATETIME NOT NULL,
`status` INT NULL DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE INDEX `ticket_UNIQUE` (`ticket` ASC)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `comment`;
CREATE TABLE `comment` (
`id` INT NOT NULL AUTO_INCREMENT,
`content` TEXT NOT NULL,
`user_id` INT NOT NULL,
`entity_id` INT NOT NULL,
`entity_type` INT NOT NULL,
`created_date` DATETIME NOT NULL,
`status` INT NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
INDEX `entity_index` (`entity_id` ASC, `entity_type` ASC)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `message`;
CREATE TABLE `message` (
`id` INT NOT NULL AUTO_INCREMENT,
`from_id` INT NULL,
`to_id` INT NULL,
`content` TEXT NULL,
`created_date` DATETIME NULL,
`has_read` INT NULL,
`conversation_id` VARCHAR(45) NOT NULL,
PRIMARY KEY (`id`),
INDEX `conversation_index` (`conversation_id` ASC),
INDEX `created_date` (`created_date` ASC))
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;

手动初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
insert  user(name,password,salt,head_url) values('user1','111111','','http://images.nowcoder.com/head/111t.png');
insert user(name,password,salt,head_url) values('user2','222222','','http://images.nowcoder.com/head/222t.png');
insert user(name,password,salt,head_url) values('user3','333333','','http://images.nowcoder.com/head/333t.png');


insert news(title, link, image, like_count, comment_count, created_date, user_id) values('TITLE1','http://www.nowcoder.com/1.html','http://images.nowcoder.com/head/1m.png',2,1,20180802,1);
insert news(title, link, image, like_count, comment_count, created_date, user_id) values('TITLE2','http://www.nowcoder.com/2.html','http://images.nowcoder.com/head/1m.png',3,2,20180802,2);
insert news(title, link, image, like_count, comment_count, created_date, user_id) values('TITLE3','http://www.nowcoder.com/3.html','http://images.nowcoder.com/head/1m.png',1,1,20180802,3);

insert login_ticket(user_id, expired, status, ticket) values(1,20180802,0,"TICKET1");
insert login_ticket(user_id, expired, status, ticket) values(2,20180802,0,"TICKET2");
insert login_ticket(user_id, expired, status, ticket) values(3,20180802,0,"TICKET3");


insert comment(user_id, content, created_date, entity_id, entity_type, status) values(1,"Comment 1~~~",20180802,1,1,0);
insert comment(user_id, content, created_date, entity_id, entity_type, status) values(2,"Comment 2~~~",20180802,2,1,0);
insert comment(user_id, content, created_date, entity_id, entity_type, status) values(3,"Comment 3~~~",20180802,3,1,0);

手动初始化toutiao2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(64) NOT NULL DEFAULT '',
`password` varchar(128) NOT NULL DEFAULT '',
`salt` varchar(32) NOT NULL DEFAULT '',
`head_url` varchar(256) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `news`;
CREATE TABLE `news` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(128) NOT NULL DEFAULT '',
`link` varchar(256) NOT NULL DEFAULT '',
`image` varchar(256) NOT NULL DEFAULT '',
`like_count` int NOT NULL,
`comment_count` int NOT NULL,
`created_date` datetime NOT NULL,
`user_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `loginticket`;
CREATE TABLE `loginticket` (
`id` INT NOT NULL AUTO_INCREMENT,
`user_id` INT NOT NULL,
`ticket` VARCHAR(45) NOT NULL,
`expired` DATETIME NOT NULL,
`status` INT NULL DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE INDEX `ticket_UNIQUE` (`ticket` ASC)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `comment`;
CREATE TABLE `comment` (
`id` INT NOT NULL AUTO_INCREMENT,
`content` TEXT NOT NULL,
`user_id` INT NOT NULL,
`entity_id` INT NOT NULL,
`entity_type` INT NOT NULL,
`created_date` DATETIME NOT NULL,
`status` INT NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
INDEX `entity_index` (`entity_id` ASC, `entity_type` ASC)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `message`;
CREATE TABLE `message` (
`id` INT NOT NULL AUTO_INCREMENT,
`from_id` INT NULL,
`to_id` INT NULL,
`content` TEXT NULL,
`created_date` DATETIME NULL,
`has_read` INT NULL,
`conversation_id` VARCHAR(45) NOT NULL,
PRIMARY KEY (`id`),
INDEX `conversation_index` (`conversation_id` ASC),
INDEX `created_date` (`created_date` ASC))
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
insert  user(name,password,salt,head_url) values('user1','111111','','http://images.nowcoder.com/head/111t.png');
insert user(name,password,salt,head_url) values('user2','222222','','http://images.nowcoder.com/head/222t.png');
insert user(name,password,salt,head_url) values('user3','333333','','http://images.nowcoder.com/head/333t.png');


insert news(title, link, image, like_count, comment_count, created_date, user_id) values('TITLE1','http://www.nowcoder.com/1.html','http://images.nowcoder.com/head/1m.png',2,1,20180802,1);
insert news(title, link, image, like_count, comment_count, created_date, user_id) values('TITLE2','http://www.nowcoder.com/2.html','http://images.nowcoder.com/head/1m.png',3,2,20180802,2);
insert news(title, link, image, like_count, comment_count, created_date, user_id) values('TITLE3','http://www.nowcoder.com/3.html','http://images.nowcoder.com/head/1m.png',1,1,20180802,3);

insert loginticket(user_id, expired, status, ticket) values(1,20180802,0,"TICKET1");
insert loginticket(user_id, expired, status, ticket) values(2,20180802,0,"TICKET2");
insert loginticket(user_id, expired, status, ticket) values(3,20180802,0,"TICKET3");


insert comment(user_id, content, created_date, entity_id, entity_type, status) values(1,"Comment 1~~~",20180802,1,1,0);
insert comment(user_id, content, created_date, entity_id, entity_type, status) values(2,"Comment 2~~~",20180802,2,1,0);
insert comment(user_id, content, created_date, entity_id, entity_type, status) values(3,"Comment 3~~~",20180802,3,1,0);
> >