├── src ├── main │ ├── resources │ │ ├── templates │ │ │ ├── header.vm │ │ │ ├── mails │ │ │ │ └── welcome.html │ │ │ ├── news.vm │ │ │ ├── footer.html │ │ │ ├── letterDetail.html │ │ │ ├── header.html │ │ │ ├── letter.html │ │ │ ├── detail.html │ │ │ └── home.html │ │ ├── static │ │ │ ├── images │ │ │ │ ├── res │ │ │ │ │ ├── qrcode.png │ │ │ │ │ └── app-iphone-frame.png │ │ │ │ └── img │ │ │ │ │ └── pop-close.png │ │ │ ├── fonts │ │ │ │ ├── Flat-UI-Icons.eot │ │ │ │ ├── Flat-UI-Icons.ttf │ │ │ │ ├── Flat-UI-Icons.woff │ │ │ │ ├── fontawesome-webfont.eot │ │ │ │ ├── fontawesome-webfont.ttf │ │ │ │ └── fontawesome-webfont.woff │ │ │ └── scripts │ │ │ │ └── main │ │ │ │ ├── base │ │ │ │ ├── util.js │ │ │ │ ├── event.js │ │ │ │ ├── upload.js │ │ │ │ └── base.js │ │ │ │ ├── site │ │ │ │ ├── test.js │ │ │ │ ├── detail.js │ │ │ │ └── home.js │ │ │ │ ├── util │ │ │ │ └── action.js │ │ │ │ └── component │ │ │ │ ├── popup.js │ │ │ │ ├── popupUpload.js │ │ │ │ ├── upload.js │ │ │ │ ├── component.js │ │ │ │ └── popupLogin.js │ │ ├── toolbox.xml │ │ ├── sql │ │ │ ├── user.sql │ │ │ └── news.sql │ │ ├── application.properties │ │ ├── com │ │ │ └── nowcoder │ │ │ │ └── dao │ │ │ │ └── NewsDAO.xml │ │ └── mybatis-config.xml │ └── java │ │ └── com │ │ └── nowcoder │ │ ├── model │ │ ├── EntityType.java │ │ ├── ViewObject.java │ │ ├── HostHolder.java │ │ ├── LoginTicket.java │ │ ├── User.java │ │ ├── Comment.java │ │ ├── Message.java │ │ └── News.java │ │ ├── service │ │ ├── ToutiaoService.java │ │ ├── CommentService.java │ │ ├── MessageService.java │ │ ├── LikeService.java │ │ ├── QiniuService.java │ │ ├── AliService.java │ │ ├── NewsService.java │ │ └── UserService.java │ │ ├── async │ │ ├── EventType.java │ │ ├── EventHandler.java │ │ ├── EventProducer.java │ │ ├── handler │ │ │ ├── LoginExceptionHandler.java │ │ │ └── LikeHandler.java │ │ ├── EventModel.java │ │ └── EventConsumer.java │ │ ├── controller │ │ ├── SettingController.java │ │ ├── UserController.java │ │ ├── HomeController.java │ │ ├── LikeController.java │ │ ├── LoginController.java │ │ ├── MessageController.java │ │ ├── IndexController.java │ │ └── NewsController.java │ │ ├── ToutiaoApplication.java │ │ ├── util │ │ ├── RedisKeyUtil.java │ │ ├── ToutiaoUtil.java │ │ ├── MailSender.java │ │ └── JedisAdapter.java │ │ ├── dao │ │ ├── UserDAO.java │ │ ├── CommentDAO.java │ │ ├── NewsDAO.java │ │ ├── LoginTicketDAO.java │ │ └── MessageDAO.java │ │ ├── aspect │ │ └── LogAspect.java │ │ ├── configuration │ │ └── ToutiaoWebConfiguration.java │ │ └── interceptor │ │ ├── LoginRequiredInterceptor.java │ │ └── PassportInterceptor.java └── test │ ├── java │ └── com │ │ └── nowcoder │ │ ├── ToutiaoApplicationTests.java │ │ ├── JedisTests.java │ │ ├── LikeServiceTests.java │ │ ├── InitDatabaseTests.java │ │ └── MultiThread.java │ └── resources │ └── init-schema.sql ├── pom.xml └── README.md /src/main/resources/templates/header.vm: -------------------------------------------------------------------------------- 1 | Title $!title -------------------------------------------------------------------------------- /src/main/resources/templates/mails/welcome.html: -------------------------------------------------------------------------------- 1 | Welcome, $username! -------------------------------------------------------------------------------- /src/main/resources/static/images/res/qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h2pl/toutiao/HEAD/src/main/resources/static/images/res/qrcode.png -------------------------------------------------------------------------------- /src/main/resources/static/fonts/Flat-UI-Icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h2pl/toutiao/HEAD/src/main/resources/static/fonts/Flat-UI-Icons.eot -------------------------------------------------------------------------------- /src/main/resources/static/fonts/Flat-UI-Icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h2pl/toutiao/HEAD/src/main/resources/static/fonts/Flat-UI-Icons.ttf -------------------------------------------------------------------------------- /src/main/resources/static/fonts/Flat-UI-Icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h2pl/toutiao/HEAD/src/main/resources/static/fonts/Flat-UI-Icons.woff -------------------------------------------------------------------------------- /src/main/resources/static/images/img/pop-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h2pl/toutiao/HEAD/src/main/resources/static/images/img/pop-close.png -------------------------------------------------------------------------------- /src/main/resources/static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h2pl/toutiao/HEAD/src/main/resources/static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /src/main/resources/static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h2pl/toutiao/HEAD/src/main/resources/static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /src/main/resources/static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h2pl/toutiao/HEAD/src/main/resources/static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /src/main/resources/static/images/res/app-iphone-frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/h2pl/toutiao/HEAD/src/main/resources/static/images/res/app-iphone-frame.png -------------------------------------------------------------------------------- /src/main/resources/toolbox.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | date 4 | application 5 | org.apache.velocity.tools.generic.DateTool 6 | 7 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/model/EntityType.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.model; 2 | 3 | /** 4 | * Created by nowcoder on 2016/7/7. 5 | */ 6 | public class EntityType { 7 | public static int ENTITY_NEWS = 1; 8 | public static int ENTITY_COMMENT = 2; 9 | } 10 | -------------------------------------------------------------------------------- /src/main/resources/sql/user.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS `user`; 2 | CREATE TABLE `user` ( 3 | `id` int(11) NOT NULL AUTO_INCREMENT, 4 | `name` varchar(64) NOT NULL, 5 | `password` varchar(64) NOT NULL, 6 | PRIMARY KEY (`id`), 7 | UNIQUE KEY `name_UNIQUE` (`name`) 8 | ) ENGINE=InnoDB AUTO_INCREMENT=34 DEFAULT CHARSET=utf8 -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/service/ToutiaoService.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.service; 2 | 3 | import org.springframework.stereotype.Service; 4 | 5 | /** 6 | * Created by 周杰伦 on 2018/5/2. 7 | */ 8 | @Service 9 | public class ToutiaoService { 10 | public String say() { 11 | return "this is from toutiaoService"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/async/EventType.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.async; 2 | 3 | /** 4 | * Created by 周杰伦 on 2018/5/8. 5 | */ 6 | //事件类型枚举类 7 | public enum EventType { 8 | LIKE(0), 9 | COMMENT(1), 10 | LOGIN(2), 11 | MAIL(3), 12 | SCORE(4); 13 | 14 | private int value; 15 | EventType(int value) { 16 | this.value = value; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/resources/static/scripts/main/base/util.js: -------------------------------------------------------------------------------- 1 | (function (window, undefined) { 2 | var Util = Base.createClass('main.base.Util'); 3 | $.extend(Util, { 4 | isEmail: fIsEmail 5 | }); 6 | 7 | function fIsEmail(sEmail) { 8 | sEmail = $.trim(sEmail); 9 | return sEmail && /^([a-zA-Z0-9_\.\-\+])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/.test(sEmail); 10 | } 11 | })(window); -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/async/EventHandler.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.async; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Created by 周杰伦 on 2018/5/8. 9 | */ 10 | //用来处理事件的接口 11 | @Component 12 | public interface EventHandler { 13 | //处理事件,不同实现类处理方式不同 14 | void doHandle(EventModel eventModel); 15 | //获取该handler可以处理的所有事件类型 16 | List getSupportEventTypes(); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/resources/sql/news.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS 'news'; 2 | CREATE TABLE 'news'( 3 | `id` int(11) unsigned NOT NULL AUTO_INCREAMENT, 4 | `titie` varchar(128) NOT NULL DEFAULT '', 5 | `link` varchar(256) NOT NULL DEFAULT '', 6 | `image` varchar(256) NOT NULL DEFAULT '', 7 | `like_count` int(11) NOT NULL, 8 | `comment_count` int(11) NOT NULL, 9 | `create_date` datetime NOT NULL, 10 | `user_id` int(11) NOT NULL, 11 | PRIMARY KEY (`id`) 12 | )ENGINE = InnoDB AUTO_INCREAMENT=11 DEFAULT CHARSET=utf8 -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/model/ViewObject.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.model; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.Objects; 6 | 7 | /** 8 | * Created by rainday on 16/6/30. 9 | */ 10 | public class ViewObject { 11 | private Map objs = new HashMap(); 12 | public void set(String key, Object value) { 13 | objs.put(key, value); 14 | } 15 | 16 | public Object get(String key) { 17 | return objs.get(key); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/controller/SettingController.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.controller; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.ResponseBody; 6 | 7 | /** 8 | * Created by nowcoder on 2016/6/26. 9 | */ 10 | @Controller 11 | public class SettingController { 12 | @RequestMapping("/setting") 13 | @ResponseBody 14 | public String setting() { 15 | return "Setting:OK"; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8088 2 | # DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties) 3 | spring.datasource.url=jdbc:mysql://localhost:3306/toutiao?useUnicode=true&characterEncoding=utf8&useSSL=false 4 | spring.datasource.username=root 5 | spring.datasource.password=1234 6 | mybatis.config-location=classpath:mybatis-config.xml 7 | # VELOCITY TEMPLATES (VelocityAutoConfiguration) 8 | spring.velocity.suffix=.html 9 | spring.velocity.cache=false 10 | spring.velocity.toolbox-config-location=toolbox.xml 11 | -------------------------------------------------------------------------------- /src/main/resources/static/scripts/main/site/test.js: -------------------------------------------------------------------------------- 1 | (function (window, undefined) { 2 | var PopupLogin = Base.getClass('main.component.PopupLogin'); 3 | 4 | Base.ready({ 5 | initialize: fInitialize 6 | }); 7 | 8 | function fInitialize() { 9 | PopupLogin.show({ 10 | listeners: { 11 | login: function () { 12 | alert('登录'); 13 | }, 14 | register: function () { 15 | alert('注册'); 16 | } 17 | } 18 | }); 19 | } 20 | 21 | })(window); -------------------------------------------------------------------------------- /src/test/java/com/nowcoder/ToutiaoApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 7 | import org.springframework.test.context.web.WebAppConfiguration; 8 | 9 | @RunWith(SpringJUnit4ClassRunner.class) 10 | @SpringBootTest(classes = ToutiaoApplication.class) 11 | @WebAppConfiguration 12 | public class ToutiaoApplicationTests { 13 | 14 | @Test 15 | public void contextLoads() { 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/model/HostHolder.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.model; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | /** 6 | * Created by 周杰伦 on 2018/5/4. 7 | */ 8 | //保存当前访问的用户信息,通过拦截器拦截时注入 9 | @Component 10 | public class HostHolder { 11 | //使用threadlocal,保证多线程中的数据独占。 12 | private static ThreadLocal users = new ThreadLocal(); 13 | 14 | public User getUser() { 15 | return users.get(); 16 | } 17 | 18 | public void setUsers(User user) { 19 | users.set(user); 20 | } 21 | 22 | public void clear() { 23 | users.remove(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/ToutiaoApplication.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder; 2 | import org.springframework.boot.SpringApplication; 3 | import org.springframework.boot.autoconfigure.SpringBootApplication; 4 | import org.springframework.boot.builder.SpringApplicationBuilder; 5 | import org.springframework.boot.web.support.SpringBootServletInitializer; 6 | 7 | @SpringBootApplication 8 | public class ToutiaoApplication extends SpringBootServletInitializer{ 9 | 10 | @Override 11 | protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { 12 | return builder.sources(ToutiaoApplication.class); 13 | } 14 | 15 | public static void main(String[] args) { 16 | SpringApplication.run(ToutiaoApplication.class, args); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/resources/com/nowcoder/dao/NewsDAO.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | news 6 | id,title, link, image, like_count, comment_count,created_date,user_id 7 | 8 | 20 | 21 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/util/RedisKeyUtil.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.util; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | /** 7 | * Created by nowcoder on 2016/7/13. 8 | */ 9 | public class RedisKeyUtil { 10 | private static String SPLIT = ":"; 11 | private static String BIZ_LIKE = "LIKE"; 12 | private static String BIZ_DISLIKE = "DISLIKE"; 13 | private static String BIZ_EVENT = "EVENT"; 14 | 15 | public static String getEventQueueKey() { 16 | return BIZ_EVENT; 17 | } 18 | 19 | public static String getLikeKey(int entityId, int entityType) { 20 | return BIZ_LIKE + SPLIT + String.valueOf(entityType) + SPLIT + String.valueOf(entityId); 21 | } 22 | 23 | public static String getDisLikeKey(int entityId, int entityType) { 24 | return BIZ_DISLIKE + SPLIT + String.valueOf(entityType) + SPLIT + String.valueOf(entityId); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/service/CommentService.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.service; 2 | 3 | import com.nowcoder.dao.CommentDAO; 4 | import com.nowcoder.model.Comment; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * Created by nowcoder on 2016/7/7. 14 | */ 15 | @Service 16 | public class CommentService { 17 | private static final Logger logger = LoggerFactory.getLogger(QiniuService.class); 18 | 19 | @Autowired 20 | CommentDAO commentDAO; 21 | 22 | public List getCommentsByEntity(int entityId, int entityType) { 23 | return commentDAO.selectByEntity(entityId, entityType); 24 | } 25 | 26 | public int addComment(Comment comment) { 27 | return commentDAO.addComment(comment); 28 | } 29 | 30 | public int getCommentCount(int entityId, int entityType) { 31 | return commentDAO.getCommentCount(entityId, entityType); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/dao/UserDAO.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.dao; 2 | 3 | import com.nowcoder.model.User; 4 | import org.apache.ibatis.annotations.*; 5 | 6 | /** 7 | * Created by nowcoder on 2016/7/2. 8 | */ 9 | @Mapper 10 | public interface UserDAO { 11 | String TABLE_NAME = "user"; 12 | String INSET_FIELDS = " name, password, salt, head_url "; 13 | String SELECT_FIELDS = " id, name, password, salt, head_url"; 14 | 15 | @Insert({"insert into ", TABLE_NAME, "(", INSET_FIELDS, 16 | ") values (#{name},#{password},#{salt},#{headUrl})"}) 17 | int addUser(User user); 18 | 19 | @Select({"select ", SELECT_FIELDS, " from ", TABLE_NAME, " where id=#{id}"}) 20 | User selectById(int id); 21 | 22 | @Select({"select ", SELECT_FIELDS, " from ", TABLE_NAME, " where name=#{name}"}) 23 | User selectByName(String name); 24 | 25 | @Update({"update ", TABLE_NAME, " set password=#{password} where id=#{id}"}) 26 | void updatePassword(User user); 27 | 28 | @Delete({"delete from ", TABLE_NAME, " where id=#{id}"}) 29 | void deleteById(int id); 30 | } 31 | -------------------------------------------------------------------------------- /src/main/resources/templates/news.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 |
 4 | Hello VM.
 5 | 
 6 | ## 你看不到我
 7 | 
 8 | #*
 9 | 这里都看不到
10 | *#
11 | 
12 | value1:$!{value1}
13 | $!{value2}
14 | ${value3}
15 | 
16 | #foreach ($color in $colors)
17 | Color $!{foreach.index}/$!{foreach.count}: $!{color}
18 | #end
19 | 
20 | #foreach($key in $map.keySet())
21 | Number $!{foreach.index}/$!{foreach.count}: $!{key} $map.get($key)
22 | #end
23 | 
24 | #foreach($kv in $map.entrySet())
25 | Number $!{foreach.index}/$!{foreach.count}: $!{kv.key} $!{kv.value}
26 | #end
27 | 
28 | 
29 | User:$!{user.name}
30 | User:$!{user.getName()}
31 | 
32 | #set($title = "nowcoder")
33 | Include: #include("header.vm") 
34 | Parse:#parse("header.vm") 35 | 36 | #macro (render_color, $color, $index) 37 | Color By Macro $index, $color 38 | #end 39 | 40 | #foreach ($color in $colors) 41 | #render_color($color, $foreach.index) 42 | #end 43 | 44 | #set($hello = "hello") 45 | #set($hworld1 = "$!{hello} world") 46 | #set($hworld2 = '$!{hello} world') 47 | 48 | hworld1:$hworld1 49 | hworld2:$hworld2 50 | 51 | $!{colors.size()} 52 | 53 |
54 | 55 | -------------------------------------------------------------------------------- /src/main/resources/mybatis-config.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/async/EventProducer.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.async; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.nowcoder.util.JedisAdapter; 5 | import com.nowcoder.util.RedisKeyUtil; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Service; 10 | 11 | /** 12 | * Created by 周杰伦 on 2018/5/8. 13 | */ 14 | //事件生产者,负责把事件放进消息队列。 15 | @Service 16 | public class EventProducer { 17 | private static final Logger logger = LoggerFactory.getLogger(EventProducer.class); 18 | 19 | @Autowired 20 | private JedisAdapter jedisAdapter; 21 | 22 | public boolean fireEvent(EventModel eventModel) { 23 | try { 24 | String json = JSONObject.toJSONString(eventModel); 25 | String key = RedisKeyUtil.getEventQueueKey(); 26 | jedisAdapter.lpush(key, json); 27 | // jedisAdapter.setObject(eventModel); 28 | return true; 29 | }catch (Exception e) { 30 | logger.error("事件放入队列失败" + e.getMessage()); 31 | return false; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/aspect/LogAspect.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.aspect; 2 | 3 | import com.nowcoder.controller.IndexController; 4 | import org.aspectj.lang.JoinPoint; 5 | import org.aspectj.lang.annotation.After; 6 | import org.aspectj.lang.annotation.Aspect; 7 | import org.aspectj.lang.annotation.Before; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.stereotype.Component; 11 | 12 | /** 13 | * Created by 周杰伦 on 2018/5/2. 14 | */ 15 | @Aspect 16 | @Component 17 | public class LogAspect { 18 | private static final Logger logger = LoggerFactory.getLogger(LogAspect.class); 19 | 20 | @Before("execution(* com.nowcoder.controller.IndexController.*(..))") 21 | public void before(JoinPoint joinPoint) { 22 | StringBuffer sb = new StringBuffer(); 23 | for (Object arg : joinPoint.getArgs()) { 24 | sb.append("arg:" + arg.toString()); 25 | } 26 | logger.info("before method:" + sb.toString()); 27 | } 28 | 29 | @After("execution(* com.nowcoder.controller.*Controller.*(..))") 30 | public void after(JoinPoint joinPoint) { 31 | logger.info("after method:"); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/dao/CommentDAO.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.dao; 2 | 3 | import com.nowcoder.model.Comment; 4 | import org.apache.ibatis.annotations.*; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Created by nowcoder on 2016/7/2. 10 | */ 11 | @Mapper 12 | public interface CommentDAO { 13 | String TABLE_NAME = " comment "; 14 | String INSERT_FIELDS = " user_id, content, created_date, entity_id, entity_type, status "; 15 | String SELECT_FIELDS = " id, " + INSERT_FIELDS; 16 | 17 | @Insert({"insert into ", TABLE_NAME, "(", INSERT_FIELDS, 18 | ") values (#{userId},#{content},#{createdDate},#{entityId},#{entityType},#{status})"}) 19 | int addComment(Comment comment); 20 | 21 | @Select({"select ", SELECT_FIELDS, " from ", TABLE_NAME, " where entity_type=#{entityType} and entity_id=#{entityId} order by id desc "}) 22 | List selectByEntity(@Param("entityId") int entityId, @Param("entityType") int entityType); 23 | 24 | @Select({"select count(id) from ", TABLE_NAME, " where entity_type=#{entityType} and entity_id=#{entityId}"}) 25 | int getCommentCount(@Param("entityId") int entityId, @Param("entityType") int entityType); 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/model/LoginTicket.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.model; 2 | 3 | import java.util.Date; 4 | 5 | /** 6 | * Created by nowcoder on 2016/7/3. 7 | */ 8 | public class LoginTicket { 9 | private int id; 10 | private int userId; 11 | private Date expired; 12 | private int status;// 0有效,1无效 13 | private String ticket; 14 | 15 | public String getTicket() { 16 | return ticket; 17 | } 18 | 19 | public void setTicket(String ticket) { 20 | this.ticket = ticket; 21 | } 22 | 23 | public int getId() { 24 | return id; 25 | } 26 | 27 | public void setId(int id) { 28 | this.id = id; 29 | } 30 | 31 | public int getUserId() { 32 | return userId; 33 | } 34 | 35 | public void setUserId(int userId) { 36 | this.userId = userId; 37 | } 38 | 39 | public Date getExpired() { 40 | return expired; 41 | } 42 | 43 | public void setExpired(Date expired) { 44 | this.expired = expired; 45 | } 46 | 47 | public int getStatus() { 48 | return status; 49 | } 50 | 51 | public void setStatus(int status) { 52 | this.status = status; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.controller; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.ui.Model; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.ResponseBody; 7 | import org.springframework.web.servlet.ModelAndView; 8 | 9 | import java.util.Arrays; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | /** 14 | * Created by 周杰伦 on 2018/5/3. 15 | */ 16 | @Controller 17 | public class UserController { 18 | // @RequestMapping("t1") 19 | // @ResponseBody 20 | // public Model test1(Model model) { 21 | // model.addAttribute("a","b"); 22 | // return model; 23 | // } 24 | // 25 | // @RequestMapping("t2") 26 | // @ResponseBody 27 | // public ModelAndView test2(ModelAndView modelAndView) { 28 | // modelAndView.addObject(Arrays.asList(new String[]{"a","b","c"})); 29 | // return modelAndView; 30 | // } 31 | 32 | // @RequestMapping("t3") 33 | // @ResponseBody 34 | // public Map test3() { 35 | // Map map = new HashMap(); 36 | // map.put("a","b"); 37 | // return map; 38 | // } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/configuration/ToutiaoWebConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.configuration; 2 | 3 | import com.nowcoder.interceptor.LoginRequiredInterceptor; 4 | import com.nowcoder.interceptor.PassportInterceptor; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Component; 7 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 8 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 9 | 10 | /** 11 | * Created by nowcoder on 2016/7/3. 12 | */ 13 | @Component 14 | public class ToutiaoWebConfiguration extends WebMvcConfigurerAdapter { 15 | @Autowired 16 | PassportInterceptor passportInterceptor; 17 | 18 | @Autowired 19 | LoginRequiredInterceptor loginRequiredInterceptor; 20 | 21 | @Override 22 | public void addInterceptors(InterceptorRegistry registry) { 23 | registry.addInterceptor(passportInterceptor); 24 | //这个拦截器可以让没有登陆的用户无法访问某些页面、 25 | //通过url匹配指定拦截的页面。 26 | //首先要让前一个拦截器来判断用户的状态,然后根据用户状态执行后续的拦截器 27 | registry.addInterceptor(loginRequiredInterceptor).addPathPatterns("/setting*"); 28 | super.addInterceptors(registry); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/service/MessageService.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.service; 2 | 3 | import com.nowcoder.dao.MessageDAO; 4 | import com.nowcoder.dao.NewsDAO; 5 | import com.nowcoder.model.Message; 6 | import org.apache.ibatis.annotations.Param; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * Created by nowcoder on 2016/7/7. 14 | */ 15 | @Service 16 | public class MessageService { 17 | @Autowired 18 | private MessageDAO messageDAO; 19 | 20 | public int addMessage(Message message) { 21 | return messageDAO.addMessage(message); 22 | } 23 | 24 | public List getConversationList(int userId, int offset, int limit) { 25 | // conversation的总条数存在id里 26 | return messageDAO.getConversationList(userId, offset, limit); 27 | } 28 | 29 | public List getConversationDetail(String conversationId, int offset, int limit) { 30 | // conversation的总条数存在id里 31 | return messageDAO.getConversationDetail(conversationId, offset, limit); 32 | } 33 | 34 | public int getUnreadCount(int userId, String conversationId) { 35 | return messageDAO.getConversationUnReadCount(userId, conversationId); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/model/User.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.model; 2 | 3 | /** 4 | * Created by nowcoder on 2016/6/26. 5 | */ 6 | public class User { 7 | private int id; 8 | private String name; 9 | private String password; 10 | private String salt; 11 | private String headUrl; 12 | 13 | public User() { 14 | 15 | } 16 | public User(String name) { 17 | this.name = name; 18 | this.password = ""; 19 | this.salt = ""; 20 | this.headUrl = ""; 21 | } 22 | 23 | public String getName() { 24 | return name; 25 | } 26 | 27 | public void setName(String name) { 28 | this.name = name; 29 | } 30 | 31 | public String getPassword() { 32 | return password; 33 | } 34 | 35 | public void setPassword(String password) { 36 | this.password = password; 37 | } 38 | 39 | public String getSalt() { 40 | return salt; 41 | } 42 | 43 | public void setSalt(String salt) { 44 | this.salt = salt; 45 | } 46 | 47 | public String getHeadUrl() { 48 | return headUrl; 49 | } 50 | 51 | public void setHeadUrl(String headUrl) { 52 | this.headUrl = headUrl; 53 | } 54 | 55 | public int getId() { 56 | return id; 57 | } 58 | 59 | public void setId(int id) { 60 | this.id = id; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/dao/NewsDAO.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.dao; 2 | 3 | import com.nowcoder.model.News; 4 | import com.nowcoder.model.User; 5 | import org.apache.ibatis.annotations.*; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * Created by nowcoder on 2016/7/2. 11 | */ 12 | @Mapper 13 | public interface NewsDAO { 14 | String TABLE_NAME = "news"; 15 | String INSERT_FIELDS = " title, link, image, like_count, comment_count, created_date, user_id "; 16 | String SELECT_FIELDS = " id, " + INSERT_FIELDS; 17 | 18 | @Insert({"insert into ", TABLE_NAME, "(", INSERT_FIELDS, 19 | ") values (#{title},#{link},#{image},#{likeCount},#{commentCount},#{createdDate},#{userId})"}) 20 | int addNews(News news); 21 | 22 | @Select({"select ", SELECT_FIELDS , " from ", TABLE_NAME, " where id=#{id}"}) 23 | News getById(int id); 24 | 25 | @Update({"update ", TABLE_NAME, " set comment_count = #{commentCount} where id=#{id}"}) 26 | int updateCommentCount(@Param("id") int id, @Param("commentCount") int commentCount); 27 | 28 | @Update({"update ", TABLE_NAME, " set like_count = #{likeCount} where id=#{id}"}) 29 | int updateLikeCount(@Param("id") int id, @Param("likeCount") int likeCount); 30 | 31 | 32 | List selectByUserIdAndOffset(@Param("userId") int userId, @Param("offset") int offset, 33 | @Param("limit") int limit); 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/dao/LoginTicketDAO.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.dao; 2 | 3 | import com.nowcoder.model.LoginTicket; 4 | import com.nowcoder.model.User; 5 | import org.apache.ibatis.annotations.*; 6 | 7 | /** 8 | * Created by nowcoder on 2016/7/2. 9 | */ 10 | @Mapper 11 | public interface LoginTicketDAO { 12 | String TABLE_NAME = " login_ticket "; 13 | String INSET_FIELDS = " user_id, ticket, expired, status "; 14 | String SELECT_FIELDS = " id, user_id, ticket, expired, status "; 15 | 16 | @Insert({"insert into ", TABLE_NAME, "(", INSET_FIELDS, 17 | ") values (#{userId},#{ticket},#{expired},#{status})"}) 18 | int addTicket(LoginTicket loginTicket); 19 | 20 | @Select({"select ", SELECT_FIELDS, " from ", TABLE_NAME, " where id=#{id}"}) 21 | LoginTicket selectById(int id); 22 | 23 | @Select({"select ", SELECT_FIELDS, " from ", TABLE_NAME, " where user_id=#{userId}"}) 24 | LoginTicket selectByName(int userId); 25 | 26 | @Select({"select ", SELECT_FIELDS, " from ", TABLE_NAME, " where ticket=#{ticket}"}) 27 | LoginTicket selectByTicket(String ticket); 28 | 29 | //此处的@Param用于传入多个参数时,区分不同的参数,因为单凭变量名没办法判断谁是谁 30 | @Update({"update ", TABLE_NAME, " set status=#{status} where ticket=#{ticket}"}) 31 | void updateStatus(@Param(value = "ticket") String ticket, @Param(value = "status") int status); 32 | 33 | @Delete({"delete from ", TABLE_NAME, " where id=#{id}"}) 34 | void deleteById(int id); 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/async/handler/LoginExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.async.handler; 2 | 3 | import com.nowcoder.async.EventHandler; 4 | import com.nowcoder.async.EventModel; 5 | import com.nowcoder.async.EventProducer; 6 | import com.nowcoder.async.EventType; 7 | import com.nowcoder.model.Message; 8 | import com.nowcoder.model.User; 9 | import com.nowcoder.service.MessageService; 10 | import com.nowcoder.service.UserService; 11 | import com.nowcoder.util.MailSender; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.stereotype.Component; 14 | 15 | import java.util.*; 16 | 17 | /** 18 | * Created by 周杰伦 on 2018/5/8. 19 | */ 20 | @Component 21 | public class LoginExceptionHandler implements EventHandler{ 22 | 23 | @Autowired 24 | MailSender mailSender; 25 | 26 | @Autowired 27 | MessageService messageService; 28 | 29 | @Override 30 | public void doHandle(EventModel eventModel) { 31 | Message message = new Message(); 32 | message.setToId(eventModel.getActorId()); 33 | message.setCreatedDate(new Date()); 34 | message.setContent("登录异常"); 35 | message.setFromId(eventModel.getEntityOwnerId()); 36 | messageService.addMessage(message); 37 | 38 | Map map = new HashMap<>(); 39 | map.put("username", eventModel.get("username")); 40 | mailSender.sendWithHTMLTemplate(eventModel.get("email"),"登录异常","mails/welcome.html", map); 41 | } 42 | 43 | @Override 44 | public List getSupportEventTypes() { 45 | return Arrays.asList(EventType.LOGIN); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/resources/templates/footer.html: -------------------------------------------------------------------------------- 1 | 11 | 12 |
13 | 14 | 15 | 16 |

牛客网

17 |

程序员的首选学习分享平台

18 | 19 |
20 | 21 | 38 |
39 | 40 | -------------------------------------------------------------------------------- /src/test/java/com/nowcoder/JedisTests.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder; 2 | 3 | import com.nowcoder.dao.CommentDAO; 4 | import com.nowcoder.dao.LoginTicketDAO; 5 | import com.nowcoder.dao.NewsDAO; 6 | import com.nowcoder.dao.UserDAO; 7 | import com.nowcoder.model.*; 8 | import com.nowcoder.util.JedisAdapter; 9 | import org.apache.commons.lang.builder.ToStringBuilder; 10 | import org.junit.Assert; 11 | import org.junit.Test; 12 | import org.junit.runner.RunWith; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.boot.test.SpringApplicationConfiguration; 15 | import org.springframework.test.context.jdbc.Sql; 16 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 17 | 18 | import java.util.Date; 19 | import java.util.Random; 20 | 21 | @RunWith(SpringJUnit4ClassRunner.class) 22 | @SpringApplicationConfiguration(classes = ToutiaoApplication.class) 23 | public class JedisTests { 24 | @Autowired 25 | JedisAdapter jedisAdapter; 26 | 27 | @Test 28 | public void testJedis() { 29 | jedisAdapter.set("hello", "world"); 30 | Assert.assertEquals("world", jedisAdapter.get("hello")); 31 | } 32 | @Test 33 | public void testObject() { 34 | User user = new User(); 35 | user.setHeadUrl("http://images.nowcoder.com/head/100t.png"); 36 | user.setName("user1"); 37 | user.setPassword("abc"); 38 | user.setSalt("def"); 39 | jedisAdapter.setObject("user1", user); 40 | 41 | User u = jedisAdapter.getObject("user1", User.class); 42 | System.out.print(ToStringBuilder.reflectionToString(u)); 43 | 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/model/Comment.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.model; 2 | 3 | import java.util.Date; 4 | 5 | /** 6 | * Created by nowcoder on 2016/7/7. 7 | */ 8 | public class Comment { 9 | private int id; 10 | private int userId; 11 | private int entityId; 12 | private int entityType; 13 | private String content; 14 | private Date createdDate; 15 | private int status; 16 | 17 | public int getId() { 18 | return id; 19 | } 20 | 21 | public void setId(int id) { 22 | this.id = id; 23 | } 24 | 25 | public int getUserId() { 26 | return userId; 27 | } 28 | 29 | public void setUserId(int userId) { 30 | this.userId = userId; 31 | } 32 | 33 | public int getEntityId() { 34 | return entityId; 35 | } 36 | 37 | public void setEntityId(int entityId) { 38 | this.entityId = entityId; 39 | } 40 | 41 | public int getEntityType() { 42 | return entityType; 43 | } 44 | 45 | public void setEntityType(int entityType) { 46 | this.entityType = entityType; 47 | } 48 | 49 | public String getContent() { 50 | return content; 51 | } 52 | 53 | public void setContent(String content) { 54 | this.content = content; 55 | } 56 | 57 | public Date getCreatedDate() { 58 | return createdDate; 59 | } 60 | 61 | public void setCreatedDate(Date createdDate) { 62 | this.createdDate = createdDate; 63 | } 64 | 65 | public int getStatus() { 66 | return status; 67 | } 68 | 69 | public void setStatus(int status) { 70 | this.status = status; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/model/Message.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.model; 2 | 3 | import java.util.Date; 4 | 5 | /** 6 | * Created by nowcoder on 2016/7/7. 7 | */ 8 | public class Message { 9 | private int id; 10 | private int fromId; 11 | private int toId; 12 | private String content; 13 | private Date createdDate; 14 | private int hasRead; 15 | private String conversationId; 16 | 17 | public int getId() { 18 | return id; 19 | } 20 | 21 | public void setId(int id) { 22 | this.id = id; 23 | } 24 | 25 | public int getFromId() { 26 | return fromId; 27 | } 28 | 29 | public void setFromId(int fromId) { 30 | this.fromId = fromId; 31 | } 32 | 33 | public int getToId() { 34 | return toId; 35 | } 36 | 37 | public void setToId(int toId) { 38 | this.toId = toId; 39 | } 40 | 41 | public String getContent() { 42 | return content; 43 | } 44 | 45 | public void setContent(String content) { 46 | this.content = content; 47 | } 48 | 49 | public Date getCreatedDate() { 50 | return createdDate; 51 | } 52 | 53 | public void setCreatedDate(Date createdDate) { 54 | this.createdDate = createdDate; 55 | } 56 | 57 | public int getHasRead() { 58 | return hasRead; 59 | } 60 | 61 | public void setHasRead(int hasRead) { 62 | this.hasRead = hasRead; 63 | } 64 | 65 | public String getConversationId() { 66 | return conversationId; 67 | } 68 | 69 | public void setConversationId(String conversationId) { 70 | this.conversationId = conversationId; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/test/java/com/nowcoder/LikeServiceTests.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder; 2 | 3 | import com.nowcoder.service.LikeService; 4 | import org.junit.*; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.SpringApplicationConfiguration; 8 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 9 | 10 | /** 11 | * Created by nowcoder on 2016/7/22. 12 | */ 13 | @RunWith(SpringJUnit4ClassRunner.class) 14 | @SpringApplicationConfiguration(classes = ToutiaoApplication.class) 15 | public class LikeServiceTests { 16 | @Autowired 17 | LikeService likeService; 18 | 19 | @Test 20 | public void testLike() { 21 | likeService.like(123, 1, 1); 22 | Assert.assertEquals(1, likeService.getLikeStatus(123, 1, 1)); 23 | 24 | likeService.disLike(123, 1, 1); 25 | Assert.assertEquals(-1, likeService.getLikeStatus(123, 1, 1)); 26 | } 27 | 28 | @Test 29 | public void testB() { 30 | System.out.println("B"); 31 | } 32 | 33 | @Test(expected = IllegalArgumentException.class) 34 | public void testException() { 35 | throw new IllegalArgumentException("异常"); 36 | } 37 | 38 | @Before 39 | public void setUp() { 40 | System.out.println("setUp"); 41 | } 42 | 43 | @After 44 | public void tearDown() { 45 | System.out.println("tearDown"); 46 | } 47 | 48 | @BeforeClass 49 | public static void beforeClass() { 50 | System.out.println("beforeClass"); 51 | } 52 | 53 | @AfterClass 54 | public static void afterClass() { 55 | System.out.println("afterClass"); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/interceptor/LoginRequiredInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.interceptor; 2 | 3 | import com.nowcoder.dao.LoginTicketDAO; 4 | import com.nowcoder.dao.UserDAO; 5 | import com.nowcoder.model.HostHolder; 6 | import com.nowcoder.model.LoginTicket; 7 | import com.nowcoder.model.User; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Component; 10 | import org.springframework.web.servlet.HandlerInterceptor; 11 | import org.springframework.web.servlet.ModelAndView; 12 | 13 | import javax.servlet.http.Cookie; 14 | import javax.servlet.http.HttpServletRequest; 15 | import javax.servlet.http.HttpServletResponse; 16 | import java.util.Date; 17 | 18 | /** 19 | * Created by nowcoder on 2016/7/3. 20 | */ 21 | @Component 22 | public class LoginRequiredInterceptor implements HandlerInterceptor { 23 | 24 | @Autowired 25 | private HostHolder hostHolder; 26 | 27 | @Override 28 | public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception { 29 | //这个拦截器可以让没有登陆的用户无法访问某些页面、 30 | if (hostHolder.getUser() == null) { 31 | httpServletResponse.sendRedirect("/?pop=1"); 32 | return false; 33 | } 34 | return true; 35 | } 36 | 37 | @Override 38 | public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { 39 | } 40 | 41 | @Override 42 | public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/model/News.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.model; 2 | 3 | import java.util.Date; 4 | 5 | /** 6 | * Created by rainday on 16/6/30. 7 | */ 8 | public class News { 9 | 10 | private int id; 11 | 12 | private String title; 13 | 14 | private String link; 15 | 16 | private String image; 17 | 18 | private int likeCount; 19 | 20 | private int commentCount; 21 | 22 | private Date createdDate; 23 | 24 | private int userId; 25 | 26 | public int getId() { 27 | return id; 28 | } 29 | 30 | public void setId(int id) { 31 | this.id = id; 32 | } 33 | 34 | public String getTitle() { 35 | return title; 36 | } 37 | 38 | public void setTitle(String title) { 39 | this.title = title; 40 | } 41 | 42 | public String getLink() { 43 | return link; 44 | } 45 | 46 | public void setLink(String link) { 47 | this.link = link; 48 | } 49 | 50 | public String getImage() { 51 | return image; 52 | } 53 | 54 | public void setImage(String image) { 55 | this.image = image; 56 | } 57 | 58 | public int getLikeCount() { 59 | return likeCount; 60 | } 61 | 62 | public void setLikeCount(int likeCount) { 63 | this.likeCount = likeCount; 64 | } 65 | 66 | public int getCommentCount() { 67 | return commentCount; 68 | } 69 | 70 | public void setCommentCount(int commentCount) { 71 | this.commentCount = commentCount; 72 | } 73 | 74 | public Date getCreatedDate() { 75 | return createdDate; 76 | } 77 | 78 | public void setCreatedDate(Date createdDate) { 79 | this.createdDate = createdDate; 80 | } 81 | 82 | public int getUserId() { 83 | return userId; 84 | } 85 | 86 | public void setUserId(int userId) { 87 | this.userId = userId; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/resources/static/scripts/main/base/event.js: -------------------------------------------------------------------------------- 1 | (function (window, undefined) { 2 | var Event = Base.createClass('main.base.Event'); 3 | $.extend(Event, { 4 | on: fOn, 5 | emit: fEmit, 6 | unbind: fUnbind, 7 | unbindAll: fUnbindAll 8 | }); 9 | 10 | function fOn(sName, fCb) { 11 | var that = this; 12 | if (!Base.isString(sName) || !Base.isFunction(fCb)) { 13 | return; 14 | } 15 | 16 | that._cep = that._cep || {}; 17 | that._cep[sName] = that._cep[sName] || []; 18 | that._cep[sName].push(fCb); 19 | } 20 | 21 | function fEmit(sName) { 22 | var that = this; 23 | if (!that._cep || !that._cep[sName]) { 24 | return; 25 | } 26 | 27 | var aArg = [].slice.call(arguments, 1); 28 | $.each(that._cep[sName], function (_, fCb) { 29 | fCb.apply(that, aArg); 30 | }); 31 | } 32 | 33 | function fUnbind(sName, fCb) { 34 | var that = this; 35 | if (!that._cep || !that._cep[sName]) { 36 | return; 37 | } 38 | 39 | if (!fCb) { 40 | that._cep[sName].length = 0; 41 | delete that._cep[sName]; 42 | return; 43 | } 44 | 45 | var oPoll = that._cep; 46 | var aCb = oPoll[sName]; 47 | for (var i = aCb.length - 1; i >= 0; i--) { 48 | if (aCb[i] === fCb) { 49 | aCb.splice(i, 1); 50 | } 51 | } 52 | aCb.length === 0 && (delete oPoll[sName]); 53 | } 54 | 55 | function fUnbindAll() { 56 | var that = this; 57 | var oPoll = that._cep; 58 | $.each(oPoll, function (sKey) { 59 | delete oPoll[sKey]; 60 | }); 61 | } 62 | })(window); -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/async/handler/LikeHandler.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.async.handler; 2 | 3 | import com.nowcoder.async.EventHandler; 4 | import com.nowcoder.async.EventModel; 5 | import com.nowcoder.async.EventType; 6 | import com.nowcoder.model.Message; 7 | import com.nowcoder.model.User; 8 | import com.nowcoder.service.MessageService; 9 | import com.nowcoder.service.UserService; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.stereotype.Component; 14 | 15 | import java.util.Arrays; 16 | import java.util.Date; 17 | import java.util.List; 18 | 19 | /** 20 | * Created by 周杰伦 on 2018/5/8. 21 | */ 22 | @Component 23 | public class LikeHandler implements EventHandler{ 24 | private static final Logger logger = LoggerFactory.getLogger(LikeHandler.class); 25 | 26 | @Autowired 27 | private MessageService messageService; 28 | 29 | @Autowired 30 | private UserService userService; 31 | 32 | @Override 33 | //实际业务上需要信息服务通知被点赞的人 34 | public void doHandle(EventModel eventModel) { 35 | Message message = new Message(); 36 | message.setFromId(eventModel.getActorId()); 37 | message.setToId(eventModel.getEntityOwnerId()); 38 | User user = userService.getUser(eventModel.getActorId()); 39 | message.setContent("用户" + user.getName() + "赞了你的资讯" 40 | + ",http://127.0.0.1:8088/news/" + eventModel.getEntityId()); 41 | message.setCreatedDate(new Date()); 42 | messageService.addMessage(message); 43 | 44 | System.out.println("like"); 45 | logger.info(eventModel.toString()); 46 | } 47 | 48 | @Override 49 | public List getSupportEventTypes() { 50 | return Arrays.asList(EventType.LIKE); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/service/LikeService.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.service; 2 | 3 | import com.nowcoder.util.JedisAdapter; 4 | import com.nowcoder.util.RedisKeyUtil; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Service; 7 | 8 | /** 9 | * Created by nowcoder on 2016/7/13. 10 | */ 11 | @Service 12 | public class LikeService { 13 | @Autowired 14 | JedisAdapter jedisAdapter; 15 | 16 | public int getLikeStatus(int userId, int entityType, int entityId) { 17 | String likeKey = RedisKeyUtil.getLikeKey(entityId, entityType); 18 | if(jedisAdapter.sismember(likeKey, String.valueOf(userId))) { 19 | return 1; 20 | } 21 | String disLikeKey = RedisKeyUtil.getDisLikeKey(entityId, entityType); 22 | return jedisAdapter.sismember(disLikeKey, String.valueOf(userId)) ? -1 : 0; 23 | } 24 | 25 | public long like(int userId, int entityType, int entityId) { 26 | // 在喜欢集合里增加 27 | String likeKey = RedisKeyUtil.getLikeKey(entityId, entityType); 28 | jedisAdapter.sadd(likeKey, String.valueOf(userId)); 29 | // 从反对里删除 30 | String disLikeKey = RedisKeyUtil.getDisLikeKey(entityId, entityType); 31 | jedisAdapter.srem(disLikeKey, String.valueOf(userId)); 32 | return jedisAdapter.scard(likeKey); 33 | } 34 | 35 | public long disLike(int userId, int entityType, int entityId) { 36 | // 在反对集合里增加 37 | String disLikeKey = RedisKeyUtil.getDisLikeKey(entityId, entityType); 38 | jedisAdapter.sadd(disLikeKey, String.valueOf(userId)); 39 | // 从喜欢里删除 40 | String likeKey = RedisKeyUtil.getLikeKey(entityId, entityType); 41 | jedisAdapter.srem(likeKey, String.valueOf(userId)); 42 | return jedisAdapter.scard(likeKey); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/dao/MessageDAO.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.dao; 2 | 3 | import com.nowcoder.model.Comment; 4 | import com.nowcoder.model.Message; 5 | import org.apache.ibatis.annotations.Insert; 6 | import org.apache.ibatis.annotations.Mapper; 7 | import org.apache.ibatis.annotations.Param; 8 | import org.apache.ibatis.annotations.Select; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * Created by nowcoder on 2016/7/2. 14 | */ 15 | @Mapper 16 | public interface MessageDAO { 17 | String TABLE_NAME = " message "; 18 | String INSERT_FIELDS = " from_id, to_id, content, has_read, conversation_id, created_date "; 19 | String SELECT_FIELDS = " id, " + INSERT_FIELDS; 20 | 21 | @Insert({"insert into ", TABLE_NAME, "(", INSERT_FIELDS, 22 | ") values (#{fromId},#{toId},#{content},#{hasRead},#{conversationId},#{createdDate})"}) 23 | int addMessage(Message message); 24 | 25 | @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 id desc limit #{offset},#{limit}"}) 26 | List getConversationList(@Param("userId") int userId, @Param("offset") int offset, @Param("limit") int limit); 27 | 28 | @Select({"select count(id) from ", TABLE_NAME, " where has_read = 0 and to_id=#{userId} and conversation_id=#{conversationId}"}) 29 | int getConversationUnReadCount(@Param("userId") int userId, @Param("conversationId") String conversationId); 30 | 31 | @Select({"select count(id) from ", TABLE_NAME, " where has_read = 0 and to_id=#{userId}"}) 32 | int getConversationTotalCount(@Param("userId") int userId, @Param("conversationId") String conversationId); 33 | 34 | @Select({"select ", SELECT_FIELDS, " from ", TABLE_NAME, " where conversation_id=#{conversationId} order by id desc limit #{offset},#{limit}"}) 35 | List getConversationDetail(@Param("conversationId") String conversationId, @Param("offset") int offset, @Param("limit") int limit); 36 | } -------------------------------------------------------------------------------- /src/test/resources/init-schema.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS `user`; 2 | CREATE TABLE `user` ( 3 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 4 | `name` varchar(64) NOT NULL DEFAULT '', 5 | `password` varchar(128) NOT NULL DEFAULT '', 6 | `salt` varchar(32) NOT NULL DEFAULT '', 7 | `head_url` varchar(256) NOT NULL DEFAULT '', 8 | PRIMARY KEY (`id`), 9 | UNIQUE KEY `name` (`name`) 10 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 11 | 12 | DROP TABLE IF EXISTS `news`; 13 | CREATE TABLE `news` ( 14 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 15 | `title` varchar(128) NOT NULL DEFAULT '', 16 | `link` varchar(256) NOT NULL DEFAULT '', 17 | `image` varchar(256) NOT NULL DEFAULT '', 18 | `like_count` int NOT NULL, 19 | `comment_count` int NOT NULL, 20 | `created_date` datetime NOT NULL, 21 | `user_id` int(11) NOT NULL, 22 | PRIMARY KEY (`id`) 23 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 24 | 25 | DROP TABLE IF EXISTS `login_ticket`; 26 | CREATE TABLE `login_ticket` ( 27 | `id` INT NOT NULL AUTO_INCREMENT, 28 | `user_id` INT NOT NULL, 29 | `ticket` VARCHAR(45) NOT NULL, 30 | `expired` DATETIME NOT NULL, 31 | `status` INT NULL DEFAULT 0, 32 | PRIMARY KEY (`id`), 33 | UNIQUE INDEX `ticket_UNIQUE` (`ticket` ASC) 34 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 35 | 36 | DROP TABLE IF EXISTS `comment`; 37 | CREATE TABLE `comment` ( 38 | `id` INT NOT NULL AUTO_INCREMENT, 39 | `content` TEXT NOT NULL, 40 | `user_id` INT NOT NULL, 41 | `entity_id` INT NOT NULL, 42 | `entity_type` INT NOT NULL, 43 | `created_date` DATETIME NOT NULL, 44 | `status` INT NOT NULL DEFAULT 0, 45 | PRIMARY KEY (`id`), 46 | INDEX `entity_index` (`entity_id` ASC, `entity_type` ASC) 47 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 48 | 49 | DROP TABLE IF EXISTS `message`; 50 | CREATE TABLE `message` ( 51 | `id` INT NOT NULL AUTO_INCREMENT, 52 | `from_id` INT NULL, 53 | `to_id` INT NULL, 54 | `content` TEXT NULL, 55 | `created_date` DATETIME NULL, 56 | `has_read` INT NULL, 57 | `conversation_id` VARCHAR(45) NOT NULL, 58 | PRIMARY KEY (`id`), 59 | INDEX `conversation_index` (`conversation_id` ASC), 60 | INDEX `created_date` (`created_date` ASC)) 61 | ENGINE = InnoDB 62 | DEFAULT CHARACTER SET = utf8; -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/async/EventModel.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.async; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * Created by 周杰伦 on 2018/5/8. 8 | */ 9 | //事件实体 10 | public class EventModel { 11 | private EventType eventType; 12 | private int actorId;//触发者 13 | //触发对象 14 | private int entityId; 15 | private int entityType; 16 | //触发对象拥有者,拥有者会收到通知 17 | private int entityOwnerId; 18 | 19 | //触发现场的信息保存 20 | private Map exts = new HashMap<>(); 21 | //使用return this可以让代码执行链路化操作,可以直接set set set 22 | //后面的set都改用这种方式 23 | public EventModel set(String key, String value) { 24 | exts.put(key, value); 25 | return this; 26 | } 27 | 28 | public String get(String key) { 29 | return exts.get(key); 30 | } 31 | 32 | 33 | public EventModel(EventType eventType) { 34 | this.eventType = eventType; 35 | } 36 | public EventModel() { 37 | 38 | } 39 | 40 | public int getActorId() { 41 | return actorId; 42 | } 43 | 44 | public EventModel setActorId(int actorId) { 45 | this.actorId = actorId; 46 | return this; 47 | } 48 | 49 | public int getEntityId() { 50 | return entityId; 51 | } 52 | 53 | public EventModel setEntityId(int entityId) { 54 | this.entityId = entityId; 55 | return this; 56 | } 57 | 58 | public int getEntityType() { 59 | return entityType; 60 | } 61 | 62 | public EventModel setEntityType(int entityType) { 63 | this.entityType = entityType; 64 | return this; 65 | } 66 | 67 | public int getEntityOwnerId() { 68 | return entityOwnerId; 69 | } 70 | 71 | public EventModel setEntityOwnerId(int entityOwnerId) { 72 | this.entityOwnerId = entityOwnerId; 73 | return this; 74 | } 75 | 76 | public EventType getEventType() { 77 | return eventType; 78 | } 79 | 80 | public EventModel setEventType(EventType eventType) { 81 | this.eventType = eventType; 82 | return this; 83 | } 84 | 85 | public Map getExts() { 86 | return exts; 87 | } 88 | 89 | public void setExts(Map exts) { 90 | this.exts = exts; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/resources/static/scripts/main/util/action.js: -------------------------------------------------------------------------------- 1 | (function (window, undefined) { 2 | var Action = Base.createClass('main.util.Action'); 3 | $.extend(Action, { 4 | like: fLike, 5 | dislike: fDislike, 6 | post: fPost 7 | }); 8 | 9 | /** 10 | * 喜欢 11 | * @param {Object} oConf 12 | * @param {String} oConf.newsId 对象id 13 | * @param {Function} oConf.call 成功回调 14 | * @param {Function} oConf.error 失败回调 15 | * @param {Function} oConf.always 操作的回调 16 | */ 17 | function fLike(oConf) { 18 | var that = this; 19 | that.post({ 20 | url: '/like', 21 | data: {newsId: oConf.newsId}, 22 | call: oConf.call, 23 | error: oConf.error, 24 | always: oConf.always 25 | }); 26 | } 27 | 28 | /** 29 | * 不喜欢 30 | * @param {Object} oConf 31 | * @param {String} oConf.newsId 对象id 32 | * @param {Function} oConf.call 成功回调 33 | * @param {Function} oConf.error 失败回调 34 | * @param {Function} oConf.always 操作的回调 35 | */ 36 | function fDislike(oConf) { 37 | var that = this; 38 | that.post({ 39 | url: '/dislike', 40 | data: {newsId: oConf.newsId}, 41 | call: oConf.call, 42 | error: oConf.error, 43 | always: oConf.always 44 | }); 45 | } 46 | 47 | /** 48 | * 简单的 ajax 请求封装 49 | * @param {Object} oConf 50 | * @param {String} oConf.method 请求类型 51 | * @param {String} oConf.url 请求连接 52 | * @param {Object} oConf.data 发送参数 53 | * @param {Function} oConf.call 成功回调 54 | * @param {Function} oConf.error 失败回调 55 | * @param {Function} oConf.always 操作的回调 56 | */ 57 | function fPost(oConf) { 58 | var that = this; 59 | $.ajax({ 60 | method: oConf.method || 'POST', 61 | url: oConf.url, 62 | dataType: 'json', 63 | data: oConf.data 64 | }).done(function (oResult) { 65 | var nCode = oResult.code; 66 | nCode === 0 && oConf.call && oConf.call(oResult); 67 | nCode !== 0 && oConf.error && oConf.error(oResult); 68 | }).fail(oConf.error).always(oConf.always); 69 | } 70 | 71 | 72 | })(window); -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/controller/HomeController.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.controller; 2 | 3 | import com.nowcoder.model.EntityType; 4 | import com.nowcoder.model.HostHolder; 5 | import com.nowcoder.model.News; 6 | import com.nowcoder.model.ViewObject; 7 | import com.nowcoder.service.LikeService; 8 | import com.nowcoder.service.NewsService; 9 | import com.nowcoder.service.UserService; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Controller; 12 | import org.springframework.ui.Model; 13 | import org.springframework.web.bind.annotation.*; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | /** 19 | * Created by nowcoder on 2016/7/2. 20 | */ 21 | @Controller 22 | public class HomeController { 23 | @Autowired 24 | NewsService newsService; 25 | 26 | @Autowired 27 | UserService userService; 28 | 29 | @Autowired 30 | HostHolder hostHolder; 31 | 32 | @Autowired 33 | LikeService likeService; 34 | 35 | private List getNews(int userId, int offset, int limit) { 36 | List newsList = newsService.getLatestNews(userId, offset, limit); 37 | int localUserId = hostHolder.getUser() != null ? hostHolder.getUser().getId() : 0; 38 | List vos = new ArrayList<>(); 39 | for (News news : newsList) { 40 | ViewObject vo = new ViewObject(); 41 | vo.set("news", news); 42 | vo.set("user", userService.getUser(news.getUserId())); 43 | if (localUserId != 0) { 44 | vo.set("like", likeService.getLikeStatus(localUserId, EntityType.ENTITY_NEWS, news.getId())); 45 | } else { 46 | vo.set("like", 0); 47 | } 48 | vos.add(vo); 49 | } 50 | return vos; 51 | } 52 | 53 | @RequestMapping(path = {"/", "/index"}, method = {RequestMethod.GET, RequestMethod.POST}) 54 | public String index(Model model, 55 | @RequestParam(value = "pop", defaultValue = "0") int pop) { 56 | model.addAttribute("vos", getNews(0, 0, 10)); 57 | model.addAttribute("pop", pop); 58 | return "home"; 59 | } 60 | 61 | @RequestMapping(path = {"/user/{userId}"}, method = {RequestMethod.GET, RequestMethod.POST}) 62 | public String userIndex(Model model, @PathVariable("userId") int userId) { 63 | model.addAttribute("vos", getNews(userId, 0, 10)); 64 | return "home"; 65 | } 66 | 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/controller/LikeController.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.controller; 2 | 3 | //import com.nowcoder.async.EventModel; 4 | //import com.nowcoder.async.EventProducer; 5 | //import com.nowcoder.async.EventType; 6 | import com.nowcoder.async.EventModel; 7 | import com.nowcoder.async.EventProducer; 8 | import com.nowcoder.async.EventType; 9 | import com.nowcoder.model.EntityType; 10 | import com.nowcoder.model.HostHolder; 11 | import com.nowcoder.model.News; 12 | import com.nowcoder.service.LikeService; 13 | import com.nowcoder.service.NewsService; 14 | import com.nowcoder.util.ToutiaoUtil; 15 | import org.apache.ibatis.annotations.Param; 16 | import org.springframework.beans.factory.annotation.Autowired; 17 | import org.springframework.stereotype.Controller; 18 | import org.springframework.web.bind.annotation.RequestMapping; 19 | import org.springframework.web.bind.annotation.RequestMethod; 20 | import org.springframework.web.bind.annotation.ResponseBody; 21 | 22 | /** 23 | * Created by nowcoder on 2016/7/13. 24 | */ 25 | @Controller 26 | public class LikeController { 27 | @Autowired 28 | LikeService likeService; 29 | 30 | @Autowired 31 | HostHolder hostHolder; 32 | 33 | @Autowired 34 | NewsService newsService; 35 | 36 | @Autowired 37 | EventProducer eventProducer; 38 | 39 | // @Autowired 40 | // EventProducer eventProducer; 41 | 42 | @RequestMapping(path = {"/like"}, method = {RequestMethod.GET, RequestMethod.POST}) 43 | @ResponseBody 44 | public String like(@Param("newId") int newsId) { 45 | long likeCount = likeService.like(hostHolder.getUser().getId(), EntityType.ENTITY_NEWS, newsId); 46 | // 更新喜欢数 47 | News news = newsService.getById(newsId); 48 | newsService.updateLikeCount(newsId, (int) likeCount); 49 | eventProducer.fireEvent(new EventModel(EventType.LIKE) 50 | .setEntityOwnerId(news.getUserId()) 51 | .setActorId(hostHolder.getUser().getId()).setEntityId(newsId)); 52 | return ToutiaoUtil.getJSONString(0, String.valueOf(likeCount)); 53 | } 54 | 55 | @RequestMapping(path = {"/dislike"}, method = {RequestMethod.GET, RequestMethod.POST}) 56 | @ResponseBody 57 | public String dislike(@Param("newId") int newsId) { 58 | long likeCount = likeService.disLike(hostHolder.getUser().getId(), EntityType.ENTITY_NEWS, newsId); 59 | // 更新喜欢数 60 | newsService.updateLikeCount(newsId, (int) likeCount); 61 | return ToutiaoUtil.getJSONString(0, String.valueOf(likeCount)); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/service/QiniuService.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.service; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.nowcoder.util.ToutiaoUtil; 5 | import com.qiniu.common.QiniuException; 6 | import com.qiniu.http.Response; 7 | import com.qiniu.storage.UploadManager; 8 | import com.qiniu.util.Auth; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.stereotype.Service; 12 | import org.springframework.web.multipart.MultipartFile; 13 | 14 | import java.io.IOException; 15 | import java.util.UUID; 16 | 17 | /** 18 | * Created by nowcoder on 2016/7/7. 19 | */ 20 | @Service 21 | public class QiniuService { 22 | private static final Logger logger = LoggerFactory.getLogger(QiniuService.class); 23 | //设置好账号的ACCESS_KEY和SECRET_KEY 24 | String ACCESS_KEY = "DEYWFFCReQqVMYESzB4qo9WYwYtxmVf-8DHU34Jr"; 25 | String SECRET_KEY = "5bFWFSYb9Gyx5v-5wYnket58cashXLw2-aZ6D3g2"; 26 | //要上传的空间 27 | String bucketname = "toutiao"; 28 | 29 | //密钥配置 30 | Auth auth = Auth.create(ACCESS_KEY, SECRET_KEY); 31 | //创建上传对象 32 | UploadManager uploadManager = new UploadManager(); 33 | 34 | private static String QINIU_IMAGE_DOMAIN = "http://p8aa1ssg4.bkt.clouddn.com/"; 35 | 36 | //简单上传,使用默认策略,只需要设置上传的空间名就可以了 37 | public String getUpToken() { 38 | return auth.uploadToken(bucketname); 39 | } 40 | 41 | public String saveImage(MultipartFile file) throws IOException { 42 | try { 43 | int dotPos = file.getOriginalFilename().lastIndexOf("."); 44 | if (dotPos < 0) { 45 | return null; 46 | } 47 | String fileExt = file.getOriginalFilename().substring(dotPos + 1).toLowerCase(); 48 | if (!ToutiaoUtil.isFileAllowed(fileExt)) { 49 | return null; 50 | } 51 | 52 | String fileName = UUID.randomUUID().toString().replaceAll("-", "") + "." + fileExt; 53 | //调用put方法上传 54 | Response res = uploadManager.put(file.getBytes(), fileName, getUpToken()); 55 | //打印返回的信息 56 | if (res.isOK() && res.isJson()) { 57 | return QINIU_IMAGE_DOMAIN + JSONObject.parseObject(res.bodyString()).get("key"); 58 | } else { 59 | logger.error("七牛异常:" + res.bodyString()); 60 | return null; 61 | } 62 | } catch (QiniuException e) { 63 | // 请求失败时打印的异常的信息 64 | logger.error("七牛异常:" + e.getMessage()); 65 | return null; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/service/AliService.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.service; 2 | 3 | import com.aliyun.oss.OSSClient; 4 | import com.aliyun.oss.model.PutObjectResult; 5 | import com.nowcoder.controller.NewsController; 6 | import com.nowcoder.util.ToutiaoUtil; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.stereotype.Service; 10 | import org.springframework.web.multipart.MultipartFile; 11 | 12 | import java.io.IOException; 13 | import java.util.UUID; 14 | 15 | /** 16 | * Created by 周杰伦 on 2018/5/5. 17 | */ 18 | @Service 19 | public class AliService { 20 | private static final Logger logger = LoggerFactory.getLogger(AliService.class); 21 | 22 | // endpoint以杭州为例,其它region请按实际情况填写 23 | public static String url = "https://lightnote.oss-cn-shanghai.aliyuncs.com/"; 24 | public static String endpoint = "oss-cn-shanghai.aliyuncs.com"; 25 | // 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录 https://ram.console.aliyun.com 创建RAM账号 26 | public static String accessKeyId = "LTAIAel4wbQD6svJ"; 27 | public static String accessKeySecret = "err8nTBbNGz0egoahI1PoNXQ4AX6kU"; 28 | public static String bucketName = "lightnote"; 29 | //上传文件 30 | public String saveImage(MultipartFile file) throws IOException { 31 | int dotPos = file.getOriginalFilename().lastIndexOf("."); 32 | if (dotPos < 0) { 33 | return null; 34 | } 35 | String fileExt = file.getOriginalFilename().substring(dotPos + 1).toLowerCase(); 36 | if (!ToutiaoUtil.isFileAllowed(fileExt)) { 37 | return null; 38 | } 39 | 40 | String fileName = UUID.randomUUID().toString().replaceAll("-", "") + "." + fileExt; 41 | // Files.copy(file.getInputStream(), new File(ToutiaoUtil.IMAGE_DIR + fileName).toPath(), 42 | // StandardCopyOption.REPLACE_EXISTING); 43 | String url = upload(fileName, file); 44 | return url; 45 | } 46 | private String upload(String key, MultipartFile file) { 47 | 48 | // 创建OSSClient实例 49 | OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret); 50 | // 上传文件 51 | PutObjectResult res; 52 | try { 53 | res = ossClient.putObject(bucketName, key, file.getInputStream()); 54 | if (res == null)return null; 55 | System.out.println(res.getETag()); 56 | return url + key; 57 | } catch (IOException e) { 58 | logger.error("上传失败" + e.getMessage()); 59 | } 60 | // 关闭client 61 | ossClient.shutdown(); 62 | return null; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/resources/templates/letterDetail.html: -------------------------------------------------------------------------------- 1 | #parse("header.html") 2 |
3 |
4 |
    5 | #foreach($msg in $messages) 6 |
  • 7 | 8 | 头像 9 | 10 |
    11 |
    12 |
    13 |
    14 |

    $date.format('yyyy-MM-dd HH:mm:ss', $!{msg.message.createdDate})

    15 | 删除 16 |
    17 |

    18 | $!{msg.message.content} 19 |

    20 |
    21 |
    22 |
  • 23 | #end 24 |
25 | 26 |
27 | 68 |
69 | #parse("footer.html") -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/service/NewsService.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.service; 2 | 3 | import com.nowcoder.dao.NewsDAO; 4 | import com.nowcoder.model.News; 5 | import com.nowcoder.util.ToutiaoUtil; 6 | import org.apache.commons.lang.ArrayUtils; 7 | import org.apache.commons.lang.StringUtils; 8 | import org.apache.tomcat.util.http.fileupload.FileUtils; 9 | import org.apache.velocity.texen.util.FileUtil; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Service; 12 | import org.springframework.web.multipart.MultipartFile; 13 | 14 | import java.io.File; 15 | import java.io.IOException; 16 | import java.nio.file.Files; 17 | import java.nio.file.StandardCopyOption; 18 | import java.util.List; 19 | import java.util.UUID; 20 | 21 | /** 22 | * Created by nowcoder on 2016/7/2. 23 | */ 24 | @Service 25 | public class NewsService { 26 | @Autowired 27 | private NewsDAO newsDAO; 28 | 29 | public List getLatestNews(int userId, int offset, int limit) { 30 | return newsDAO.selectByUserIdAndOffset(userId, offset, limit); 31 | } 32 | 33 | public int addNews(News news) { 34 | newsDAO.addNews(news); 35 | return news.getId(); 36 | } 37 | 38 | public News getById(int newsId) { 39 | return newsDAO.getById(newsId); 40 | } 41 | 42 | //上传文件 43 | public String saveImage(MultipartFile file) throws IOException { 44 | int dotPos = file.getOriginalFilename().lastIndexOf("."); 45 | if (dotPos < 0) { 46 | return null; 47 | } 48 | String fileExt = file.getOriginalFilename().substring(dotPos + 1).toLowerCase(); 49 | if (!ToutiaoUtil.isFileAllowed(fileExt)) { 50 | return null; 51 | } 52 | 53 | String fileName = UUID.randomUUID().toString().replaceAll("-", "") + "." + fileExt; 54 | Files.copy(file.getInputStream(), new File(ToutiaoUtil.IMAGE_DIR + fileName).toPath(), 55 | StandardCopyOption.REPLACE_EXISTING); 56 | return ToutiaoUtil.TOUTIAO_DOMAIN + "image?name=" + fileName; 57 | } 58 | 59 | //上传多张图片 60 | public String saveImages(MultipartFile[] files) throws IOException { 61 | StringBuffer sb = new StringBuffer(); 62 | for (MultipartFile file : files) { 63 | String fileUrl = saveImage(file); 64 | sb.append(","); 65 | sb.append(fileUrl); 66 | } 67 | return sb.toString(); 68 | } 69 | 70 | public int updateCommentCount(int id, int count) { 71 | return newsDAO.updateCommentCount(id, count); 72 | } 73 | 74 | public int updateLikeCount(int id, int count) { 75 | return newsDAO.updateLikeCount(id, count); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/resources/static/scripts/main/base/upload.js: -------------------------------------------------------------------------------- 1 | (function (window, undefined) { 2 | var Upload = Base.createClass('main.base.Upload'); 3 | $.extend(Upload, { 4 | support: fSupport, 5 | uploadFile: fUploadFile, 6 | uploadBlob: fUploadBlob, 7 | uploadFormData: fUploadFormData, 8 | parseJSON: fParseJSON 9 | }); 10 | 11 | function fSupport() { 12 | return !!window.FormData && !!window.XMLHttpRequest && !!window.JSON && !!window.addEventListener; 13 | } 14 | 15 | function fUploadFile(oFile, oConf) { 16 | var that = this; 17 | var oData = new FormData(); 18 | oData.append(oConf.name || 'file', oFile); 19 | that.uploadFormData(oData, oConf); 20 | } 21 | 22 | function fUploadBlob(oBlob, sFileName, oConf) { 23 | var that = this; 24 | var oData = new FormData(); 25 | oData.append(oConf.name || 'file', oBlob, sFileName); 26 | that.uploadFormData(oData, oConf); 27 | } 28 | 29 | /** 30 | * 上传数据 31 | * @param {Object} oData FormData 对象 32 | * @param {Object} oConf 配置参数 33 | * @param {String} oConf.name 上传数据的name 34 | * @param {Object} oConf.data 其他额外需要发送给服务器的数据 35 | * @param {Function} oConf.progress 上传进度回调 36 | * @param {Function} oConf.call 上传成功回调 37 | * @param {Function} oConf.error 上传失败回调 38 | */ 39 | function fUploadFormData(oData, oConf) { 40 | var that = this; 41 | var XMLHttpRequest = window.XMLHttpRequest; 42 | $.each(oConf.data, function (sKey, sVal) { 43 | sKey && sVal && oData.append(sKey, sVal); 44 | }); 45 | var oXhr = new XMLHttpRequest(); 46 | // 进度 47 | oXhr.upload.addEventListener('progress', function(oEvent) { 48 | oConf.progress && oConf.progress.call(null, Math.round(oEvent.loaded * 100 / (oEvent.total || 1)), oEvent.loaded, oEvent.total); 49 | }, false); 50 | // 结果 51 | oXhr.onreadystatechange = function(oEvent) { 52 | if (oXhr.readyState !== 4) { 53 | return; 54 | } 55 | 56 | if (oXhr.status == 200) { 57 | var oResult = that.parseJSON(oXhr.responseText); 58 | var fCb = oConf[oResult.code === 0 ? 'call' : 'error']; 59 | fCb && fCb.call(null, oResult); 60 | } else { 61 | oConf.error && oConf.error.call(that, {msg: '出现错误,请重试'}); 62 | } 63 | }; 64 | oXhr.open('POST', oConf.url, true); 65 | oXhr.send(oData); 66 | } 67 | 68 | function fParseJSON(sStr) { 69 | var oResult = {}; 70 | try { 71 | oResult = JSON.parse(sStr); 72 | } catch (e) {} 73 | return oResult; 74 | } 75 | })(window); -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/util/ToutiaoUtil.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.util; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.nowcoder.controller.LoginController; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import java.security.MessageDigest; 9 | import java.util.Map; 10 | 11 | /** 12 | * Created by nowcoder on 2016/7/3. 13 | */ 14 | public class ToutiaoUtil { 15 | private static final Logger logger = LoggerFactory.getLogger(ToutiaoUtil.class); 16 | public static final String IMAGE_DIR = "D:/toutiao/upload/"; 17 | public static final String[] IMAGE_FILE_EXT = {"png", "jpg", "jpeg", "bmp"}; 18 | public static String TOUTIAO_DOMAIN = "http://127.0.0.1:8080/"; 19 | 20 | public static String getJSONString(int code) { 21 | JSONObject json = new JSONObject(); 22 | json.put("code", code); 23 | return json.toJSONString(); 24 | } 25 | 26 | public static String getJSONString(int code, String msg) { 27 | JSONObject json = new JSONObject(); 28 | json.put("code", code); 29 | json.put("msg", msg); 30 | return json.toJSONString(); 31 | } 32 | 33 | public static String getJSONString(int code, Map map) { 34 | JSONObject json = new JSONObject(); 35 | json.put("code", code); 36 | for (Map.Entry entry : map.entrySet()) { 37 | json.put(entry.getKey(), entry.getValue()); 38 | } 39 | return json.toJSONString(); 40 | } 41 | 42 | public static String MD5(String key) { 43 | char hexDigits[] = { 44 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' 45 | }; 46 | try { 47 | byte[] btInput = key.getBytes(); 48 | // 获得MD5摘要算法的 MessageDigest 对象 49 | MessageDigest mdInst = MessageDigest.getInstance("MD5"); 50 | // 使用指定的字节更新摘要 51 | mdInst.update(btInput); 52 | // 获得密文 53 | byte[] md = mdInst.digest(); 54 | // 把密文转换成十六进制的字符串形式 55 | int j = md.length; 56 | char str[] = new char[j * 2]; 57 | int k = 0; 58 | for (int i = 0; i < j; i++) { 59 | byte byte0 = md[i]; 60 | str[k++] = hexDigits[byte0 >>> 4 & 0xf]; 61 | str[k++] = hexDigits[byte0 & 0xf]; 62 | } 63 | return new String(str); 64 | } catch (Exception e) { 65 | logger.error("生成MD5失败", e); 66 | return null; 67 | } 68 | } 69 | 70 | public static boolean isFileAllowed(String fileExt) { 71 | for (String ext : IMAGE_FILE_EXT) { 72 | if (fileExt.equals(ext)) { 73 | return true; 74 | } 75 | } 76 | return false; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/util/MailSender.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.util; 2 | 3 | import org.apache.velocity.app.VelocityEngine; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.InitializingBean; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.mail.javamail.JavaMailSenderImpl; 9 | import org.springframework.mail.javamail.MimeMessageHelper; 10 | import org.springframework.stereotype.Service; 11 | 12 | import javax.mail.internet.MimeUtility; 13 | import org.springframework.ui.velocity.VelocityEngineUtils; 14 | 15 | import javax.mail.internet.InternetAddress; 16 | import javax.mail.internet.MimeMessage; 17 | import java.util.Map; 18 | import java.util.Properties; 19 | 20 | /** 21 | * Created by nowcoder on 2016/7/15. // course@nowcoder.com NKnk66 22 | */ 23 | @Service 24 | public class MailSender implements InitializingBean { 25 | private static final Logger logger = LoggerFactory.getLogger(MailSender.class); 26 | private JavaMailSenderImpl mailSender; 27 | 28 | @Autowired 29 | private VelocityEngine velocityEngine; 30 | 31 | public boolean sendWithHTMLTemplate(String to, String subject, 32 | String template, Map model) { 33 | try { 34 | String nick = MimeUtility.encodeText("牛客中级课"); 35 | InternetAddress from = new InternetAddress("dasdas"); 36 | MimeMessage mimeMessage = mailSender.createMimeMessage(); 37 | MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage); 38 | String result = VelocityEngineUtils 39 | .mergeTemplateIntoString(velocityEngine, template, "UTF-8", model); 40 | mimeMessageHelper.setTo(to); 41 | mimeMessageHelper.setFrom(from); 42 | mimeMessageHelper.setSubject(subject); 43 | mimeMessageHelper.setText(result, true); 44 | mailSender.send(mimeMessage); 45 | return true; 46 | } catch (Exception e) { 47 | logger.error("发送邮件失败" + e.getMessage()); 48 | return false; 49 | } 50 | } 51 | 52 | @Override 53 | public void afterPropertiesSet() throws Exception { 54 | mailSender = new JavaMailSenderImpl(); 55 | 56 | // 请输入自己的邮箱和密码,用于发送邮件 57 | mailSender.setUsername("asdasd"); 58 | mailSender.setPassword("dasdas"); 59 | mailSender.setHost("smtp.exmail.qq.com"); 60 | // 请配置自己的邮箱和密码 61 | 62 | mailSender.setPort(465); 63 | mailSender.setProtocol("smtps"); 64 | mailSender.setDefaultEncoding("utf8"); 65 | Properties javaMailProperties = new Properties(); 66 | javaMailProperties.put("mail.smtp.ssl.enable", true); 67 | mailSender.setJavaMailProperties(javaMailProperties); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/resources/templates/header.html: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/interceptor/PassportInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.interceptor; 2 | 3 | import com.nowcoder.dao.LoginTicketDAO; 4 | import com.nowcoder.dao.UserDAO; 5 | import com.nowcoder.model.HostHolder; 6 | import com.nowcoder.model.LoginTicket; 7 | import com.nowcoder.model.User; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Component; 10 | import org.springframework.web.servlet.HandlerInterceptor; 11 | import org.springframework.web.servlet.ModelAndView; 12 | 13 | import javax.servlet.http.Cookie; 14 | import javax.servlet.http.HttpServletRequest; 15 | import javax.servlet.http.HttpServletResponse; 16 | import java.util.Date; 17 | 18 | /** 19 | * Created by 周杰伦 on 2018/5/4. 20 | */ 21 | @Component 22 | public class PassportInterceptor implements HandlerInterceptor{ 23 | @Autowired 24 | private UserDAO userDAO; 25 | 26 | @Autowired 27 | private LoginTicketDAO loginTicketDAO; 28 | 29 | @Autowired 30 | private HostHolder hostHolder; 31 | 32 | //true继续请求,false拒绝请求 33 | @Override 34 | public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception { 35 | //处理用户信息,判断是否有ticket,一个用户一个ticket,但是有时限 36 | String ticket = null; 37 | if (httpServletRequest.getCookies() != null) { 38 | for (Cookie cookie : httpServletRequest.getCookies()) { 39 | if (cookie.getName().equals("ticket")) { 40 | ticket = cookie.getValue(); 41 | break; 42 | } 43 | } 44 | //判断ticket是否过期和无效 45 | if (ticket != null) { 46 | LoginTicket loginTicket = loginTicketDAO.selectByTicket(ticket); 47 | if (loginTicket == null || loginTicket.getExpired().before(new Date()) || loginTicket.getStatus() != 0) { 48 | return true; 49 | } 50 | else { 51 | User user = userDAO.selectById(loginTicket.getUserId()); 52 | hostHolder.setUsers(user); 53 | return true; 54 | //不能直接放在request里,因为是全局的一个ticket,其他服务想要读取时可能不会用到httprequest请求, 55 | // 但是可以注入hostholder来获取用户信息。 56 | } 57 | } 58 | } 59 | return true; 60 | } 61 | 62 | //渲染之前提供的后处理方法,可以添加模型数据,自动传给前端。 63 | @Override 64 | public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { 65 | if (modelAndView != null && hostHolder.getUser() != null) { 66 | modelAndView.addObject(hostHolder.getUser()); 67 | hostHolder.clear(); 68 | } 69 | } 70 | 71 | @Override 72 | public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { 73 | 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.nowcoder 7 | toutiao 8 | 0.0.1-SNAPSHOT 9 | war 10 | 11 | toutiao 12 | Demo project for Spring Boot 13 | 14 | 15 | 16 | org.springframework.boot 17 | spring-boot-starter-parent 18 | 1.4.0.RELEASE 19 | 20 | 21 | 22 | 23 | UTF-8 24 | 1.8 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-aop 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-velocity 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-web 39 | 40 | 41 | 42 | 43 | org.mybatis.spring.boot 44 | mybatis-spring-boot-starter 45 | 1.2.1 46 | 47 | 48 | mysql 49 | mysql-connector-java 50 | 5.1.6 51 | runtime 52 | 53 | 54 | redis.clients 55 | jedis 56 | 2.8.0 57 | 58 | 59 | 60 | 61 | 62 | org.springframework.boot 63 | spring-boot-devtools 64 | 65 | 66 | javax.mail 67 | mail 68 | 1.4.7 69 | 70 | 71 | 72 | com.qiniu 73 | qiniu-java-sdk 74 | 7.1.1 75 | 76 | 77 | com.aliyun.oss 78 | aliyun-sdk-oss 79 | 2.8.3 80 | 81 | 82 | org.springframework.boot 83 | spring-boot-starter-test 84 | test 85 | 86 | 87 | com.alibaba 88 | fastjson 89 | 1.2.22 90 | 91 | 92 | 93 | 94 | 95 | 96 | org.springframework.boot 97 | spring-boot-maven-plugin 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /src/main/resources/static/scripts/main/site/detail.js: -------------------------------------------------------------------------------- 1 | (function (window, undefined) { 2 | var PopupLogin = Base.getClass('main.component.PopupLogin'); 3 | var PopupUpload = Base.getClass('main.component.PopupUpload'); 4 | var ActionUtil = Base.getClass('main.util.Action'); 5 | 6 | Base.ready({ 7 | initialize: fInitialize, 8 | binds: { 9 | //.表示class #表示id 10 | 'click .js-login': fClickLogin, 11 | 'click .js-share': fClickShare 12 | }, 13 | events: { 14 | 'click button.click-like': fClickLike, 15 | 'click button.click-dislike': fClickDisLike 16 | } 17 | }); 18 | 19 | function fInitialize() { 20 | if (window.loginpop > 0) { 21 | fClickLogin(); 22 | } 23 | } 24 | function fClickShare() { 25 | var that = this; 26 | PopupUpload.show({ 27 | listeners: { 28 | done: function () { 29 | //alert('login'); 30 | window.location.reload(); 31 | } 32 | } 33 | }); 34 | } 35 | function fClickLogin() { 36 | var that = this; 37 | PopupLogin.show({ 38 | listeners: { 39 | login: function () { 40 | //alert('login'); 41 | window.location.reload(); 42 | }, 43 | register: function () { 44 | //alert('reg'); 45 | window.location.reload(); 46 | } 47 | } 48 | }); 49 | } 50 | 51 | function fClickLike(oEvent) { 52 | var that = this; 53 | var oEl = $(oEvent.currentTarget); 54 | var sId = $.trim(oEl.attr('data-id')); 55 | // 已经操作过 || 不存在Id || 正在提交 ,则忽略 56 | if (oEl.hasClass('pressed') || !sId || that.actioning) { 57 | return; 58 | } 59 | that.actioning = true; 60 | ActionUtil.like({ 61 | newsId: sId, 62 | call: function (oResult) { 63 | oEl.find('span.count').html(oResult.msg); 64 | oEl.addClass('pressed'); 65 | oEl.parent().find('.click-dislike').removeClass('pressed'); 66 | }, 67 | error: function () { 68 | alert('出现错误,请重试'); 69 | }, 70 | always: function () { 71 | that.actioning = false; 72 | } 73 | }); 74 | } 75 | 76 | function fClickDisLike(oEvent) { 77 | var that = this; 78 | var oEl = $(oEvent.currentTarget); 79 | var sId = $.trim(oEl.attr('data-id')); 80 | // 已经操作过 || 不存在Id || 正在提交 ,则忽略 81 | if (oEl.hasClass('pressed') || !sId || that.actioning) { 82 | return; 83 | } 84 | that.actioning = true; 85 | ActionUtil.dislike({ 86 | newsId: sId, 87 | call: function (oResult) { 88 | oEl.addClass('pressed'); 89 | var oLikeBtn = oEl.parent().find('.click-like'); 90 | oLikeBtn.removeClass('pressed'); 91 | oLikeBtn.find('span.count').html(oResult.msg); 92 | }, 93 | error: function () { 94 | alert('出现错误,请重试'); 95 | }, 96 | always: function () { 97 | that.actioning = false; 98 | } 99 | }); 100 | } 101 | 102 | })(window); -------------------------------------------------------------------------------- /src/main/resources/static/scripts/main/site/home.js: -------------------------------------------------------------------------------- 1 | (function (window, undefined) { 2 | var PopupLogin = Base.getClass('main.component.PopupLogin'); 3 | var PopupUpload = Base.getClass('main.component.PopupUpload'); 4 | var ActionUtil = Base.getClass('main.util.Action'); 5 | 6 | Base.ready({ 7 | initialize: fInitialize, 8 | binds: { 9 | //.表示class #表示id 10 | 'click .js-login': fClickLogin, 11 | 'click .js-share': fClickShare 12 | }, 13 | events: { 14 | 'click button.click-like': fClickLike, 15 | 'click button.click-dislike': fClickDisLike 16 | } 17 | }); 18 | 19 | function fInitialize() { 20 | if (window.loginpop > 0) { 21 | fClickLogin(); 22 | } 23 | } 24 | function fClickShare() { 25 | var that = this; 26 | PopupUpload.show({ 27 | listeners: { 28 | done: function () { 29 | //alert('login'); 30 | window.location.reload(); 31 | } 32 | } 33 | }); 34 | } 35 | function fClickLogin() { 36 | var that = this; 37 | PopupLogin.show({ 38 | listeners: { 39 | login: function () { 40 | //alert('login'); 41 | window.location.reload(); 42 | }, 43 | register: function () { 44 | //alert('reg'); 45 | window.location.reload(); 46 | } 47 | } 48 | }); 49 | } 50 | 51 | function fClickLike(oEvent) { 52 | var that = this; 53 | var oEl = $(oEvent.currentTarget); 54 | var sId = $.trim(oEl.attr('data-id')); 55 | // 已经操作过 || 不存在Id || 正在提交 ,则忽略 56 | if (oEl.hasClass('pressed') || !sId || that.actioning) { 57 | return; 58 | } 59 | that.actioning = true; 60 | ActionUtil.like({ 61 | newsId: sId, 62 | call: function (oResult) { 63 | oEl.find('span.count').html(oResult.msg); 64 | oEl.addClass('pressed'); 65 | oEl.parent().find('.click-dislike').removeClass('pressed'); 66 | }, 67 | error: function () { 68 | alert('出现错误,请重试'); 69 | }, 70 | always: function () { 71 | that.actioning = false; 72 | } 73 | }); 74 | } 75 | 76 | function fClickDisLike(oEvent) { 77 | var that = this; 78 | var oEl = $(oEvent.currentTarget); 79 | var sId = $.trim(oEl.attr('data-id')); 80 | // 已经操作过 || 不存在Id || 正在提交 ,则忽略 81 | if (oEl.hasClass('pressed') || !sId || that.actioning) { 82 | return; 83 | } 84 | that.actioning = true; 85 | ActionUtil.dislike({ 86 | newsId: sId, 87 | call: function (oResult) { 88 | oEl.addClass('pressed'); 89 | var oLikeBtn = oEl.parent().find('.click-like'); 90 | oLikeBtn.removeClass('pressed'); 91 | oLikeBtn.find('span.count').html(oResult.msg); 92 | }, 93 | error: function () { 94 | alert('出现错误,请重试'); 95 | }, 96 | always: function () { 97 | that.actioning = false; 98 | } 99 | }); 100 | } 101 | 102 | })(window); -------------------------------------------------------------------------------- /src/test/java/com/nowcoder/InitDatabaseTests.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder; 2 | 3 | import com.nowcoder.dao.CommentDAO; 4 | import com.nowcoder.dao.LoginTicketDAO; 5 | import com.nowcoder.dao.NewsDAO; 6 | import com.nowcoder.dao.UserDAO; 7 | import com.nowcoder.model.*; 8 | import org.junit.Assert; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.boot.test.SpringApplicationConfiguration; 13 | import org.springframework.test.context.jdbc.Sql; 14 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 15 | 16 | import java.util.Date; 17 | import java.util.Random; 18 | 19 | @RunWith(SpringJUnit4ClassRunner.class) 20 | @SpringApplicationConfiguration(classes = ToutiaoApplication.class) 21 | @Sql("/init-schema.sql") 22 | public class InitDatabaseTests { 23 | @Autowired 24 | UserDAO userDAO; 25 | 26 | @Autowired 27 | NewsDAO newsDAO; 28 | 29 | @Autowired 30 | LoginTicketDAO loginTicketDAO; 31 | 32 | @Autowired 33 | CommentDAO commentDAO; 34 | 35 | @Test 36 | public void initData() { 37 | Random random = new Random(); 38 | for (int i = 0; i < 11; ++i) { 39 | User user = new User(); 40 | user.setHeadUrl(String.format("http://images.nowcoder.com/head/%dt.png", random.nextInt(1000))); 41 | user.setName(String.format("USER%d", i)); 42 | user.setPassword(""); 43 | user.setSalt(""); 44 | userDAO.addUser(user); 45 | 46 | News news = new News(); 47 | news.setCommentCount(i); 48 | Date date = new Date(); 49 | date.setTime(date.getTime() + 1000*3600*5*i); 50 | news.setCreatedDate(date); 51 | news.setImage(String.format("http://images.nowcoder.com/head/%dm.png", random.nextInt(1000))); 52 | news.setLikeCount(i+1); 53 | news.setUserId(i+1); 54 | news.setTitle(String.format("TITLE{%d}", i)); 55 | news.setLink(String.format("http://www.nowcoder.com/%d.html", i)); 56 | newsDAO.addNews(news); 57 | 58 | // 给每个资讯插入3个评论 59 | for(int j = 0; j < 3; ++j) { 60 | Comment comment = new Comment(); 61 | comment.setUserId(i+1); 62 | comment.setCreatedDate(new Date()); 63 | comment.setStatus(0); 64 | comment.setContent("这里是一个评论啊!" + String.valueOf(j)); 65 | comment.setEntityId(news.getId()); 66 | comment.setEntityType(EntityType.ENTITY_NEWS); 67 | commentDAO.addComment(comment); 68 | } 69 | 70 | user.setPassword("newpassword"); 71 | userDAO.updatePassword(user); 72 | 73 | LoginTicket ticket = new LoginTicket(); 74 | ticket.setStatus(0); 75 | ticket.setUserId(i+1); 76 | ticket.setExpired(date); 77 | ticket.setTicket(String.format("TICKET%d", i+1)); 78 | loginTicketDAO.addTicket(ticket); 79 | 80 | loginTicketDAO.updateStatus(ticket.getTicket(), 2); 81 | 82 | } 83 | 84 | Assert.assertEquals("newpassword", userDAO.selectById(1).getPassword()); 85 | userDAO.deleteById(1); 86 | Assert.assertNull(userDAO.selectById(1)); 87 | 88 | Assert.assertEquals(1, loginTicketDAO.selectByTicket("TICKET1").getUserId()); 89 | Assert.assertEquals(2, loginTicketDAO.selectByTicket("TICKET1").getStatus()); 90 | 91 | Assert.assertNotNull(commentDAO.selectByEntity(1, EntityType.ENTITY_NEWS).get(0)); 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/async/EventConsumer.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.async; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.nowcoder.util.JedisAdapter; 5 | import com.nowcoder.util.RedisKeyUtil; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.BeansException; 9 | import org.springframework.beans.factory.InitializingBean; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.context.ApplicationContext; 12 | import org.springframework.context.ApplicationContextAware; 13 | import org.springframework.stereotype.Service; 14 | 15 | import java.util.*; 16 | 17 | /** 18 | * Created by 周杰伦 on 2018/5/8. 19 | */ 20 | @Service 21 | //要使用上下文要实现ApplicationContextAware接口 22 | public class EventConsumer implements InitializingBean,ApplicationContextAware{ 23 | @Autowired 24 | private JedisAdapter jedisAdapter; 25 | 26 | //Spring上下文 27 | //consumer需要一个事件映射,也就是需要事先知道是哪个handler来处理 28 | private ApplicationContext applicationContext; 29 | private static final Logger logger = LoggerFactory.getLogger(EventConsumer.class); 30 | //事件统一处理,需要把一个事件给所有符合的handler处理,需要一个map来存储所有handler 31 | private Map> config = new HashMap<>(); 32 | 33 | //初始化完成后一直监听队列等待处理事件 34 | @Override 35 | public void afterPropertiesSet() throws Exception { 36 | //需要遍历所有handler,也就是找到所有实现handler接口的实现类 37 | //spring自带的bean容器已经提供了这么一个方法、 38 | Map beans = applicationContext.getBeansOfType(EventHandler.class); 39 | if (beans != null) { 40 | //遍历所有eventhandler,找出每个handler支持的type。 41 | for (Map.Entry entry: beans.entrySet()) { 42 | List eventTypes = entry.getValue().getSupportEventTypes(); 43 | //根据type和handler形成映射表,完成map。 44 | for (EventType type : eventTypes) { 45 | if (!config.containsKey(type)) { 46 | config.put(type, new ArrayList()); 47 | config.get(type).add(entry.getValue()); 48 | }else { 49 | config.get(type).add(entry.getValue()); 50 | } 51 | } 52 | 53 | } 54 | } 55 | 56 | //根据前面的map可以知道取出的时间由谁来处理 57 | Thread thread = new Thread(new Runnable() { 58 | @Override 59 | public void run() { 60 | while (true) { 61 | String key = RedisKeyUtil.getEventQueueKey(); 62 | List events = jedisAdapter.brpop(0, key); 63 | for (String msg : events) { 64 | //redis自带消息key要过滤掉 65 | if (msg.equals(key)) { 66 | continue; 67 | } 68 | EventModel eventModel = JSONObject.parseObject(msg, EventModel.class); 69 | if (!config.containsKey(eventModel.getEventType())) { 70 | logger.error("不能识别的事件"); 71 | continue; 72 | } 73 | for (EventHandler eventHandler : config.get(eventModel.getEventType())) { 74 | eventHandler.doHandle(eventModel); 75 | } 76 | } 77 | } 78 | } 79 | }); 80 | thread.start(); 81 | } 82 | 83 | 84 | @Override 85 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 86 | this.applicationContext = applicationContext; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/resources/templates/letter.html: -------------------------------------------------------------------------------- 1 | #parse("header.html") 2 |
3 |
4 | 40 | 41 |
42 | 83 |
84 | #parse("footer.html") -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.service; 2 | 3 | import com.nowcoder.dao.LoginTicketDAO; 4 | import com.nowcoder.dao.UserDAO; 5 | import com.nowcoder.model.LoginTicket; 6 | import com.nowcoder.model.User; 7 | import com.nowcoder.util.ToutiaoUtil; 8 | import org.apache.commons.lang.StringUtils; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.stereotype.Service; 11 | import sun.security.provider.MD5; 12 | 13 | import java.util.Date; 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | import java.util.UUID; 17 | 18 | /** 19 | * Created by 周杰伦 on 2018/5/3. 20 | */ 21 | @Service 22 | public class UserService { 23 | 24 | @Autowired 25 | private UserDAO userDAO; 26 | 27 | @Autowired 28 | private LoginTicketDAO loginTicketDAO; 29 | 30 | public Map register(String userName, String password) { 31 | Map map = new HashMap<>(); 32 | if (StringUtils.isBlank(userName)) { 33 | map.put("msg", "用户名不能为空"); 34 | return map; 35 | } 36 | if (StringUtils.isBlank(password)) { 37 | map.put("msg", "密码不能为空"); 38 | return map; 39 | } 40 | User user = userDAO.selectByName(userName); 41 | if (user != null) { 42 | map.put("msg", "用户名已被注册"); 43 | return map; 44 | } 45 | 46 | //用户名检验,敏感词,特殊符号等 47 | //密码强度 48 | 49 | 50 | user = new User(); 51 | user.setName(userName); 52 | user.setSalt(UUID.randomUUID().toString().substring(0, 5)); 53 | user.setHeadUrl(String.format("http://images.nowcoder.com/head/%dt.png",1)); 54 | user.setPassword(ToutiaoUtil.MD5(password + user.getSalt())); 55 | userDAO.addUser(user); 56 | 57 | //传入ticket,也就是登录成功 58 | String ticket = addLoginTicket(user.getId()); 59 | map.put("ticket", ticket); 60 | return map; 61 | } 62 | 63 | public Map login(String userName, String password) { 64 | Map map = new HashMap<>(); 65 | if (StringUtils.isBlank(userName)) { 66 | map.put("msg", "用户名不能为空"); 67 | return map; 68 | } 69 | if (StringUtils.isBlank(password)) { 70 | map.put("msg", "密码不能为空"); 71 | return map; 72 | } 73 | User user = userDAO.selectByName(userName); 74 | if (user == null) { 75 | map.put("msg", "用户名不存在"); 76 | return map; 77 | } 78 | if (!user.getPassword().equals(ToutiaoUtil.MD5(password + user.getSalt()))) { 79 | map.put("msg", "密码错误"); 80 | return map; 81 | } 82 | //传入ticket,也就是登录成功 83 | String ticket = addLoginTicket(user.getId()); 84 | map.put("ticket", ticket); 85 | return map; 86 | } 87 | 88 | private String addLoginTicket(int userId) { 89 | LoginTicket ticket = new LoginTicket(); 90 | ticket.setUserId(userId); 91 | Date date = new Date(); 92 | date.setTime(date.getTime() + 1000*3600*24); 93 | ticket.setExpired(date); 94 | ticket.setStatus(0); 95 | ticket.setTicket(UUID.randomUUID().toString().replaceAll("-", "")); 96 | loginTicketDAO.addTicket(ticket); 97 | return ticket.getTicket(); 98 | } 99 | 100 | 101 | public void addUser(User user) { 102 | userDAO.addUser(user); 103 | } 104 | 105 | public User getUser(int id) { 106 | return userDAO.selectById(id); 107 | } 108 | 109 | public void logout(String ticket) { 110 | loginTicketDAO.updateStatus(ticket, 1); 111 | } 112 | 113 | public void updateUser(User user) { 114 | userDAO.updatePassword(user); 115 | } 116 | 117 | public void deleteUser(int userId) { 118 | userDAO.deleteById(userId); 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/controller/LoginController.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.controller; 2 | 3 | import com.nowcoder.async.EventModel; 4 | import com.nowcoder.async.EventProducer; 5 | import com.nowcoder.async.EventType; 6 | import com.nowcoder.model.News; 7 | import com.nowcoder.model.ViewObject; 8 | import com.nowcoder.service.NewsService; 9 | import com.nowcoder.service.UserService; 10 | import com.nowcoder.util.ToutiaoUtil; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.stereotype.Controller; 15 | import org.springframework.ui.Model; 16 | import org.springframework.web.bind.annotation.*; 17 | 18 | import javax.servlet.http.Cookie; 19 | import javax.servlet.http.HttpServletResponse; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import java.util.Map; 23 | 24 | /** 25 | * Created by nowcoder on 2016/7/2. 26 | */ 27 | @Controller 28 | public class LoginController { 29 | private static final Logger logger = LoggerFactory.getLogger(LoginController.class); 30 | 31 | @Autowired 32 | private UserService userService; 33 | 34 | @Autowired 35 | private EventProducer eventProducer; 36 | 37 | @RequestMapping(path = {"/reg/"}, method = {RequestMethod.GET, RequestMethod.POST}) 38 | @ResponseBody 39 | public String reg(Model model, @RequestParam("username") String username, 40 | @RequestParam("password") String password, 41 | @RequestParam(value="rember", defaultValue = "0") int rememberme, 42 | HttpServletResponse response) { 43 | try { 44 | Map map = userService.register(username, password); 45 | if (map.containsKey("ticket")) { 46 | Cookie cookie = new Cookie("ticket", map.get("ticket").toString()); 47 | cookie.setPath("/"); 48 | if (rememberme > 0) { 49 | cookie.setMaxAge(3600*24*5); 50 | } 51 | response.addCookie(cookie); 52 | return ToutiaoUtil.getJSONString(0, "注册成功"); 53 | } else { 54 | return ToutiaoUtil.getJSONString(1, map); 55 | } 56 | 57 | } catch (Exception e) { 58 | logger.error("注册异常" + e.getMessage()); 59 | return ToutiaoUtil.getJSONString(1, "注册异常"); 60 | } 61 | } 62 | 63 | @RequestMapping(path = {"/login/"}, method = {RequestMethod.GET, RequestMethod.POST}) 64 | @ResponseBody 65 | public String login(Model model, @RequestParam("username") String username, 66 | @RequestParam("password") String password, 67 | @RequestParam(value="rember", defaultValue = "0") int rememberme, 68 | HttpServletResponse response) { 69 | try { 70 | Map map = userService.login(username, password); 71 | if (map.containsKey("ticket")) { 72 | Cookie cookie = new Cookie("ticket", map.get("ticket").toString()); 73 | cookie.setPath("/"); 74 | if (rememberme > 0) { 75 | cookie.setMaxAge(3600*24*5); 76 | } 77 | response.addCookie(cookie); 78 | eventProducer.fireEvent(new EventModel(EventType.LOGIN) 79 | .set("username", username).set("email", "362294931@qq.com")); 80 | return ToutiaoUtil.getJSONString(0, "登录成功"); 81 | } else { 82 | return ToutiaoUtil.getJSONString(1, map); 83 | } 84 | 85 | } catch (Exception e) { 86 | logger.error("登录异常功" + e.getMessage()); 87 | return ToutiaoUtil.getJSONString(1, "登录异常"); 88 | } 89 | } 90 | 91 | @RequestMapping(path = {"/logout/"}, method = {RequestMethod.GET, RequestMethod.POST}) 92 | public String logout(@CookieValue("ticket") String ticket) { 93 | userService.logout(ticket); 94 | return "redirect:/"; 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/controller/MessageController.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.controller; 2 | 3 | import com.nowcoder.model.*; 4 | import com.nowcoder.service.*; 5 | import com.nowcoder.util.ToutiaoUtil; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Controller; 10 | import org.springframework.ui.Model; 11 | import org.springframework.web.bind.annotation.*; 12 | 13 | import java.util.ArrayList; 14 | import java.util.Date; 15 | import java.util.List; 16 | 17 | /** 18 | * Created by nowcoder on 2016/7/2. 19 | */ 20 | @Controller 21 | public class MessageController { 22 | private static final Logger logger = LoggerFactory.getLogger(MessageController.class); 23 | 24 | @Autowired 25 | HostHolder hostHolder; 26 | 27 | @Autowired 28 | UserService userService; 29 | 30 | @Autowired 31 | MessageService messageService; 32 | 33 | @RequestMapping(path = {"/msg/detail"}, method = {RequestMethod.GET}) 34 | public String conversationDetail(Model model, @RequestParam("conversationId") String conversationId) { 35 | try { 36 | List messages = new ArrayList<>(); 37 | List messageList = messageService.getConversationDetail(conversationId, 0, 10); 38 | for (Message msg : messageList) { 39 | ViewObject vo = new ViewObject(); 40 | vo.set("message", msg); 41 | User user = userService.getUser(msg.getFromId()); 42 | if (user == null) { 43 | continue; 44 | } 45 | vo.set("headUrl", user.getHeadUrl()); 46 | vo.set("userName", user.getName()); 47 | messages.add(vo); 48 | } 49 | model.addAttribute("messages", messages); 50 | return "letterDetail"; 51 | } catch (Exception e) { 52 | logger.error("获取站内信列表失败" + e.getMessage()); 53 | } 54 | return "letterDetail"; 55 | } 56 | 57 | @RequestMapping(path = {"/msg/list"}, method = {RequestMethod.GET}) 58 | public String conversationList(Model model) { 59 | try { 60 | int localUserId = hostHolder.getUser().getId(); 61 | List conversations = new ArrayList<>(); 62 | List messageList = messageService.getConversationList(localUserId, 0, 10); 63 | for (Message msg : messageList) { 64 | ViewObject vo = new ViewObject(); 65 | vo.set("conversation", msg); 66 | int targetId = msg.getFromId() == localUserId ? msg.getToId() : msg.getFromId(); 67 | User user = userService.getUser(targetId); 68 | vo.set("headUrl", user.getHeadUrl()); 69 | vo.set("userName", user.getName()); 70 | vo.set("targetId", targetId); 71 | vo.set("totalCount", msg.getId()); 72 | vo.set("unreadCount", messageService.getUnreadCount(localUserId, msg.getConversationId())); 73 | conversations.add(vo); 74 | } 75 | model.addAttribute("conversations", conversations); 76 | return "letter"; 77 | } catch (Exception e) { 78 | logger.error("获取站内信列表失败" + e.getMessage()); 79 | } 80 | return "letter"; 81 | } 82 | 83 | @RequestMapping(path = {"/msg/addMessage"}, method = {RequestMethod.GET, RequestMethod.POST}) 84 | @ResponseBody 85 | public String addMessage(@RequestParam("fromId") int fromId, 86 | @RequestParam("toId") int toId, 87 | @RequestParam("content") String content) { 88 | Message msg = new Message(); 89 | msg.setContent(content); 90 | msg.setCreatedDate(new Date()); 91 | msg.setToId(toId); 92 | msg.setFromId(fromId); 93 | msg.setConversationId(fromId < toId ? String.format("%d_%d", fromId, toId) : 94 | String.format("%d_%d", toId, fromId)); 95 | messageService.addMessage(msg); 96 | return ToutiaoUtil.getJSONString(msg.getId()); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/resources/static/scripts/main/component/popup.js: -------------------------------------------------------------------------------- 1 | /** 2 | var oPopup = new Popup({ 3 | title: String, 标题 4 | content: String, 内容 5 | width: Number, 宽度 6 | close: Function, 关闭的回调 7 | hasNoHeader: Boolean, true 没有头部 8 | }); 9 | */ 10 | (function (window, undefined) { 11 | var Popup = Base.createClass('main.component.Popup'); 12 | var Component = Base.getClass('main.component.Component'); 13 | Base.mix(Popup, Component, { 14 | zIndex: 100, 15 | _tpl: [ 16 | '
', 17 | '
', 18 | '', 19 | '

#{title}

', 20 | '
', 21 | '
#{content}
', 22 | '
'].join(''), 23 | listeners: [{ 24 | name: 'render', 25 | type: 'custom', 26 | handler: function () { 27 | var that = this; 28 | var oConf = that.rawConfig; 29 | var oEl = that.getEl(); 30 | // 常用元素 31 | that.contentEl = oEl.find('div.pop-content'); 32 | // 调整大小 33 | oEl.outerWidth(oConf.width || 520); 34 | oConf.height && that.contentEl.outerHeight(oConf.height); 35 | // 禁止body滚动 36 | that.forbidScroll(document.body); 37 | // 创建遮罩层 38 | that.initMask(); 39 | // 调整z-index 40 | oEl.css('zIndex', Popup.zIndex++); 41 | // 去掉头部 42 | oConf.hasNoHeader && oEl.find('div.pop-title').remove(); 43 | // 位置居中 44 | that.fixPosition(); 45 | // 绑定窗口变化事件 46 | that.resizeCb = Base.bind(that.fixPosition, that); 47 | $(window).resize(that.resizeCb); 48 | } 49 | }, { 50 | name: 'click .js-close', 51 | handler: function () { 52 | var that = this; 53 | that.close(); 54 | } 55 | }] 56 | }, { 57 | initialize: fInitialize, 58 | initMask: fInitMask, 59 | fixPosition: fFixPosition, 60 | close: fClose, 61 | getData: fGetData 62 | }); 63 | 64 | function fInitialize(oConf) { 65 | var that = this; 66 | var oBody = $(document.body); 67 | oConf.renderTo = oBody; 68 | that.isForbidScroll = oBody.css('overflow-y') === 'hidden'; 69 | Popup.superClass.initialize.apply(that, arguments); 70 | } 71 | 72 | function fInitMask() { 73 | var that = this; 74 | var oConf = that.rawConfig; 75 | if (!that.maskEl) { 76 | that.maskEl = $('
'); 77 | oConf.renderTo.append(that.maskEl); 78 | } 79 | } 80 | 81 | function fFixPosition() { 82 | var that = this; 83 | var oEl = that.getEl(); 84 | var oWin = $(window); 85 | var oDoc = $(document); 86 | var nElWidth = oEl.width(); 87 | var nElHeight = oEl.height(); 88 | var nWinWidth = oWin.width(); 89 | var nWinHeight = oWin.height(); 90 | var nScrollTop = Math.max(oWin.scrollTop() || oDoc.scrollTop()); 91 | // 调整元素大小 92 | oEl.css({ 93 | left: nWinWidth > nElWidth ? (nWinWidth - nElWidth) / 2 : 0, 94 | top: (nWinHeight > nElHeight ? (nWinHeight - nElHeight) / 2 : 0) + nScrollTop 95 | }); 96 | // 调整遮罩层大小 97 | that.maskEl.css({ 98 | width: '100%', 99 | height: nWinHeight, 100 | top: nScrollTop 101 | }); 102 | } 103 | 104 | function fClose(bNoEmit) { 105 | var that = this; 106 | // 移除文件 107 | var oEl = that.getEl(); 108 | oEl.remove(); 109 | // 启动滚动 110 | !that.isForbidScroll && that.forbidScroll(document.body, false); 111 | // 移除遮罩层 112 | that.maskEl && that.maskEl.remove(); 113 | // 取消窗口变化事件 114 | $(window).unbind('resize', that.resizeCb); 115 | !bNoEmit && that.emit('close'); 116 | } 117 | 118 | function fGetData(oConf) { 119 | var that = this; 120 | return { 121 | title: oConf.title || '提示', 122 | content: oConf.content 123 | }; 124 | } 125 | 126 | })(window); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # toutiao 2 | 仿照今日头条的主页toutiao.com做的一个Java web项目。使用SpringBoot+Mybatis+velocity开发。开发工具和Java语言介绍 3 | 4 | 内容包括: 5 | 开发工具和Java语言 6 | Spring入门,模板语法和渲染 7 | 数据库交互iBatis集成 8 | 用户注册登录管理 9 | 资讯发布,图片上传,资讯首页 10 | 评论中心,站内信 11 | Redis入门以及Redis实现赞踩功能 12 | 异步设计和站内邮件通知系统 13 | 多种资讯排序算法 14 | JavaWeb项目测试和部署,课程总结回顾 15 | 16 | # quick-start 17 | 18 | [1项目基本配置](#项目基本配置) 19 | 20 | [2基本框架开发](#基本框架开发) 21 | 22 | [3数据库配置和首页的创建](#数据库配置和首页的创建) 23 | 24 | [4用户注册登录以及使用token](#用户注册登录以及使用token) 25 | 26 | [5上传图片至云存储,以及新增一条分享内容](#上传图片至云存储,以及新增一条分享内容) 27 | 28 | [6新增评论和站内信功能](#新增评论和站内信功能) 29 | 30 | [7新增点赞和点踩功能,使用Redis实现](#新增点赞和点踩功能,使用Redis实现) 31 | 32 | [8新增异步消息功能,新增邮件发送组件](#新增异步消息功能 新增邮件发送组件) 33 | 34 | 35 | 36 | ## 项目基础配置 37 | 38 | 创建git仓库,本地配置idea并测试pull和push。 39 | 40 | 创建SpringBoot工程,导入Web,Velocity和Aop的包。 41 | 42 | 生成Maven项目,pom.xml包含上述依赖,应用名称是toutiao,小组id是com.nowcoder。 43 | 44 | ## 基本框架开发 45 | 46 | 创建基本的controller,service和model层。 47 | 48 | controller中使用注解配置,requestmapping,responsebody基本可以解决请求转发以及响应内容的渲染。responsebody自动选择viewresolver进行解析。 49 | 50 | 使用pathvariable和requestparam传递参数。 51 | 52 | 使用velocity编写页面模板,注意其中的语法使用。常用$!{}和${} 53 | 54 | 使用http规范下的httpservletrequest和httpservletresponse来封装请求和相响应,使用封装好的session和cookie对象。 55 | 56 | 使用重定向的redirectview和统一异常处理器exceptionhandler 57 | 58 | AOP和IOC 59 | 60 | IOC解决对象实例化以及依赖传递问题,解耦。 61 | 62 | AOP解决纵向切面问题,主要实现日志和权限控制功能。 63 | 64 | aspect实现切面,并且使用logger来记录日志,用该切面的切面方法来监听controller。 65 | 66 | ## 数据库配置和首页的创建 67 | 68 | 使用mysql创建数据库和表,建议自己写一下sql到mysql命令行跑一下。 69 | 70 | 加入mybatis和mysql的maven仓库,注意,由于现在版本的springboot不再支持velocity进而导致我使用较早版本的springboot,所以这里提供一可以正常运行的版本设置。 71 | 72 | springboot使用1.4.0 73 | 74 | mybatis-spring-boot-starter使用1.2.1 75 | 76 | mysql-connector-java使用5.1.6 77 | 78 | 亲测可用。 79 | 80 | 接下来写controller,dao和service。注意mybatis的注解语法以及xml的配置要求,xml要求放在resource中并且与dao接口在相同的包路径下。 81 | 82 | application.properties增加spring配置数据库链接地址 83 | 84 | 两个小工具: 85 | ViewObject:方便传递任何数据到 86 | VelocityDateTool:velocity自带工具类 87 | 88 | 写好静态文件html css和js。并且注意需要配置 89 | spring.velocity.suffix=.html 保证跳转请求转发到html上 90 | spring.velocity.toolbox-config-location=toolbox.xml 91 | 92 | 至此主页基本完成,具体业务逻辑请参考代码。 93 | 94 | ## 用户注册登录以及使用token 95 | 96 | 完成用户注册和登录的controller,service和dao层代码 97 | 98 | 新建数据表login_ticket用来存储ticket字段。该字段在用户登录成功时被生成并存入数据库,并被设置为cookie, 99 | 下次用户登录时会带上这个ticket,ticket是随机的uuid,有过期时间以及有效状态。 100 | 101 | 使用拦截器interceptor来拦截所有用户请求,判断请求中是否有有有效的ticket,如果有的话则将用户信息写入Threadlocal。 102 | 所有线程的threadlocal都被存在一个叫做hostholder的实例中,根据该实例就可以在全局任意位置获取用户的信息。 103 | 104 | 该ticket的功能类似session,也是通过cookie写回浏览器,浏览器请求时再通过cookie传递,区别是该字段是存在数据库中的,并且可以用于移动端。 105 | 106 | 通过用户访问权限拦截器来拦截用户的越界访问,比如用户没有管理员权限就不能访问管理员页面。 107 | 108 | 配置了用户的webconfiguration来设置启动时的配置,这里可以将上述的两个拦截器加到启动项里。 109 | 110 | 配置了json工具类以及md5工具类,并且使用Java自带的盐生成api将用户密码加密为密文。保证密码安全。 111 | 112 | 数据安全性的保障手段:https使用公钥加密私钥解密,比如支付宝的密码加密,单点登录验证,验证码机制等。 113 | 114 | ajax异步加载数据 json数据传输等。 115 | 116 | 117 | ## 上传图片至云存储,以及新增一条分享内容 118 | 119 | 图片的本地上传,前端请求发送二进制图片文件流,content-type是multipart类型 120 | 121 | 后端Spring也是用multipartFile来接收该对象 122 | 123 | 接收对象后验证文件的格式和后缀是否正确,将文件名统一用uuid来命名,并加上后缀名,这样可以避免文件名重复。 124 | 125 | 可以直接使用IOStreamUitl的copy方法将文件直接拷贝到本地,返回一个本地访问路径即可。 126 | 127 | 但是本地存储大量图片显然不合适,所以可以使用七牛云或者阿里云提供的对象存储服务来存储图片。 128 | 129 | 这里使用阿里云的OSS,首先建立一个bucket,并且获取地址,key和password等信息,添加maven依赖, 130 | 直接使用一个简单的文件上传例子就可以实现上传功能,并且返回一个指向该图片存储地址的url。 131 | 132 | 新增一个分享内容的步骤:选择上传图片,调用图片上传接口并返回url,然后输入内容,将这些数据一并保存到数据库。 133 | 134 | 本次开发中使用到了fiddler工具来实现浏览器的代理,该工具可以先拦截这个请求,再进行转发,所以在工具内可以解析请求和响应内容。 135 | 136 | 通过这个工具可以很清晰地看到http请求的实现细节。通过换行符区分字段,并且在字段长度超过首部长度后,后面的contentlength长度就是请求体的内容了,里面即可以放json,也可以直接放二进制字节流,如图片。其中post方法才带有请求体,get方法是没有请求体的。 137 | 138 | ## 新增评论和站内信功能 139 | 140 | 首先建立表comment和message分别代表评论和站内信。 141 | 142 | 依次开发model,dao,service和controller。 143 | 144 | 评论的逻辑是每一条资讯下面都有评论,显示评论数量,具体内容,评论人等信息。 145 | 146 | 消息的逻辑是,两个用户之间发送一条消息,有一个唯一的会话id,这个会话里可以有多条这两个用户的交互信息。 147 | 通过一个用户id获取该用户的会话列表,再根据会话id再获取具体的会话内的多条消息。 148 | 149 | 逻辑清楚之后,再加上一些附加功能,比如显示未读消息数量,根据时间顺序排列会话和消息。 150 | 151 | 本节内容基本就是业务逻辑的开发,没有新增什么技术点,主要是前后端交互的逻辑比较复杂,前端的开发量也比较大。 152 | 153 | ## 新增点赞和点踩功能,使用Redis实现 154 | 155 | 首先了解一下redis的基础知识,数据结构,jedis使用等。 156 | 157 | 编写list,string,hashmap,set,sortset的测试用例,熟悉jedis api。 158 | 159 | 开发点踩和点赞功能,在此之前根据业务封装好jedis的增删改查操作,放在util包中 160 | 161 | 根据需求确定key字段,格式是 like:实体类型:实体id 和 dislike:实体类型:实体id 这样可以将喜欢一条新闻的人存在一个集合,不喜欢的存在另一个集合。通过统计数量可以获得点赞和点踩数。 162 | 163 | 一般点赞点踩操作是先修改redis的值并获取返回值,然后再异步修改mysql数据库的likecount数值。这样既可以保证点赞操作快速完成,也可保证数据一致性。 164 | 165 | 本次开发过程中遇到了请求超时的问题,经过排查之后是漏写了某个接口的服务,导致前端获取不到后端需要传的数据,而前端代码会不断检测这个数据的值以完成后续操作,导致页面无法完成解析。后来回滚到上一个版本后才发现bug所在并解决了该问题。 166 | 167 | ## 新增异步消息功能 新增邮件发送组件 168 | 169 | 在之前的功能中有一些不需要实时执行的操作或者任务,我们可以把它们改造成异步消息来进行发送。 170 | 171 | 具体操作就是使用redis来实现异步消息队列。代码中我们使用事件event来包装一个事件,事件需要记录事件实体的各种信息。 172 | 173 | 我们在async包里开发异步工具类,事件生产者,事件消费者,并且开发一个eventhandler接口,让各种事件的实现类来实现这个接口。 174 | 175 | 事件生产者一般作为一个服务,由业务代码进行调用产生一个事件。而事件消费者我们在代码里使用了单线程循环获取队列里的事件,并且寻找对应的handler进行处理。 176 | 177 | 如此一来,整个异步事件的框架就开发完成了。后面新加入的登录,点赞等事件都可以这么实现。 178 | 179 | 新增邮件功能,主要是引入mail依赖,并且配置好自己的邮箱信息,以及邮件模板,同时在业务代码中加入发邮件的逻辑即可。 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /src/main/resources/static/scripts/main/component/popupUpload.js: -------------------------------------------------------------------------------- 1 | /** 2 | var oPopupUpload = new PopupUpload({ 3 | 4 | }); 5 | */ 6 | (function (window) { 7 | var PopupUpload = Base.createClass('main.component.PopupUpload'); 8 | var Popup = Base.getClass('main.component.Popup'); 9 | var Upload = Base.getClass('main.component.Upload'); 10 | var Component = Base.getClass('main.component.Component'); 11 | var Util = Base.getClass('main.base.Util'); 12 | 13 | Base.mix(PopupUpload, Component, { 14 | _tpl: [ 15 | '
', 16 | '
', 17 | '
', 18 | '', 19 | '
', 20 | '上传图片', 21 | '
', 22 | '
', 23 | '
', 24 | '
', 25 | '
', 26 | '
', 27 | '', 28 | '
', 29 | '
', 30 | '
'].join(''), 31 | listeners: [{ 32 | name: 'render', 33 | type: 'custom', 34 | handler: function () { 35 | var that = this; 36 | var oEl = that.getEl(); 37 | var oUploadBtn = oEl.find('a.js-upload-btn'); 38 | new Upload({ 39 | targetEl: oUploadBtn, 40 | url: '/uploadImage/', 41 | check: function (oFile, sType, nFileSize) { 42 | var sMsg = nFileSize === 0 ? '文件大小不能为0' : /image/gi.test(sType || '') ? '' : '文件格式不正确'; 43 | sMsg && alert(sMsg); 44 | return !sMsg; 45 | }, 46 | call: function (oResult) { 47 | var sUrl = $.trim(oResult.msg); 48 | if (oResult.code !== 0) { 49 | return alert('出现错误,请重试'); 50 | } 51 | that.image = sUrl; 52 | that.showImage(sUrl); 53 | } 54 | }); 55 | } 56 | }, { 57 | name: 'click input.js-submit', 58 | handler: function () { 59 | var that = this; 60 | var oEl = that.getEl(); 61 | var sTitle = $.trim(oEl.find('input.js-title').val()); 62 | var sLink = $.trim(oEl.find('input.js-link').val()); 63 | if (!sTitle) { 64 | return alert('标题不能为空'); 65 | } 66 | if (!sLink) { 67 | return alert('链接不能为空'); 68 | } 69 | if (!that.image) { 70 | return alert('图片不能为空'); 71 | } 72 | if (that.requesting) { 73 | return; 74 | } 75 | that.requesting = true; 76 | $.ajax({ 77 | url: '/user/addNews/', 78 | method: 'post', 79 | data: {image: that.image, title: sTitle, link: sLink}, 80 | dataType: 'json' 81 | }).done(function (oResult) { 82 | that.emit('done'); 83 | }).fail(function (oResult) { 84 | alert('出现错误,请重试'); 85 | }).always(function () { 86 | that.requesting = false; 87 | }); 88 | } 89 | }], 90 | show: fStaticShow 91 | }, { 92 | initialize: fInitialize, 93 | showImage: fShowImage 94 | }); 95 | 96 | function fStaticShow(oConf) { 97 | var that = this; 98 | var oLogin = new PopupUpload(oConf); 99 | var oPopup = new Popup({ 100 | title: '分享', 101 | width: 700, 102 | content: oLogin.html() 103 | }); 104 | oLogin._popup = oPopup; 105 | Component.setEvents(); 106 | } 107 | 108 | function fInitialize(oConf) { 109 | var that = this; 110 | delete oConf.renderTo; 111 | PopupUpload.superClass.initialize.apply(that, arguments); 112 | } 113 | 114 | function fShowImage(sUrl) { 115 | var that = this; 116 | var oEl = that.getEl(); 117 | var sHtml = [ 118 | '
', 119 | '', 120 | '
', 121 | '', 122 | '
'].join(''); 123 | oEl.find('div.letter-pic-box').remove(); 124 | oEl.find('div.js-image-container').prepend(sHtml); 125 | } 126 | 127 | })(window); -------------------------------------------------------------------------------- /src/main/resources/static/scripts/main/component/upload.js: -------------------------------------------------------------------------------- 1 | /** 2 | var oUpload = new Upload({ 3 | targetEl: Object, 上传按钮 4 | name: String, 文件的name 5 | url: String, 上传路径 6 | data: Object, 发送的参数 7 | check: Function, 文件检查 8 | call: Function, 上传成功回调 9 | error: Function, 上传失败回调 10 | progress: Function, 上传进度回调 11 | }); 12 | */ 13 | (function (window, undefined) { 14 | var Upload = Base.createClass('main.component.Upload'); 15 | var BaseUpload = Base.getClass('main.base.Upload'); 16 | var Popup = Base.getClass('main.component.Popup'); 17 | var Component = Base.getClass('main.component.Component'); 18 | 19 | Base.mix(Upload, Component, { 20 | _tpl: '
', 21 | listeners: [{ 22 | name: 'render', 23 | type: 'custom', 24 | handler: function () { 25 | var that = this; 26 | var oEl = that.getEl(); 27 | that.fileEl = oEl.find('input.js-upload'); 28 | that.initTarget(); 29 | that.initInput(); 30 | } 31 | }] 32 | }, { 33 | initialize: fInitialize, 34 | initTarget: fInitTarget, 35 | initInput: fInitInput, 36 | clear: fClear 37 | }); 38 | 39 | function fInitialize(oConf) { 40 | var that = this; 41 | var oTargetEl = $(oConf.targetEl); 42 | oTargetEl.css('position', 'relative'); 43 | oConf.renderTo = oTargetEl; 44 | Upload.superClass.initialize.apply(that, arguments); 45 | } 46 | 47 | function fInitTarget() { 48 | var that = this; 49 | var oConf = that.rawConfig; 50 | var oEl = that.getEl(); 51 | var oTargetEl = $(oConf.targetEl); 52 | var nWidth = oTargetEl.outerWidth(); 53 | var nHeight = oTargetEl.outerHeight(); 54 | // 位置 55 | oEl.css({top:0, left: 0, width: nWidth, height: nHeight}); 56 | // 调整大小 57 | that.fileEl.outerWidth(nWidth); 58 | that.fileEl.outerHeight(nHeight); 59 | } 60 | 61 | function fInitInput() { 62 | var that = this; 63 | var oConf = that.rawConfig; 64 | var oIpt = that.fileEl; 65 | oIpt.on('change', function (oEvent) { 66 | var aFiles = oIpt.get(0).files; 67 | var oFile = aFiles[0]; 68 | if (!oFile) { 69 | return; 70 | } 71 | var sType = oFile.type; 72 | var nFileSize = oFile.size; 73 | // 检查是否能上传 74 | if (oConf.check && !oConf.check.call(that, oFile, sType, nFileSize)) { 75 | that.clear(); 76 | return; 77 | } 78 | // 上传文件 79 | var oPopup; 80 | BaseUpload.uploadFile(oFile, { 81 | name: oConf.name, 82 | url: oConf.url, 83 | data: oConf.data, 84 | call: function () { 85 | oPopup && oPopup.close(); 86 | oConf.call && oConf.call.apply(that, arguments); 87 | }, 88 | error: function () { 89 | oPopup && oPopup.close(); 90 | alert('出现错误,请重试'); 91 | oConf.error && oConf.error.apply(that, arguments); 92 | }, 93 | progress: function (nProgress) { 94 | if (!oPopup) { 95 | oPopup = new Popup({ 96 | content: '
正在上传:' + nProgress + '%
', 97 | hasNoHeader: true 98 | }); 99 | } 100 | oPopup.getEl().find('div.js-progress').html('正在上传:' + nProgress + '%'); 101 | oConf.progress && oConf.progress.apply(that, arguments); 102 | } 103 | }); 104 | that.clear(); 105 | }); 106 | 107 | // 进入提示条 108 | var oPopup; 109 | new BaseUpload({ 110 | input: that.fileEl, 111 | check: oConf.check, 112 | url: oConf.url, 113 | name: oConf.name, 114 | data: oConf.data, 115 | call: function () { 116 | oPopup && oPopup.close(); 117 | oConf.call && oConf.call.apply(that, arguments); 118 | }, 119 | error: function () { 120 | oPopup && oPopup.close(); 121 | alert('出现错误,请重试'); 122 | oConf.error && oConf.error.apply(that, arguments); 123 | }, 124 | progress: function (nProgress) { 125 | if (!oPopup) { 126 | oPopup = new Popup({ 127 | content: '
正在上传:' + nProgress + '%
', 128 | hasNoHeader: true 129 | }); 130 | } 131 | oPopup.getEl().find('div.js-progress').html('正在上传:' + nProgress + '%'); 132 | oConf.progress && oConf.progress.apply(that, arguments); 133 | } 134 | }); 135 | } 136 | 137 | function fClear() { 138 | var that = this; 139 | that.fileEl.val(''); 140 | } 141 | 142 | })(window); -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/controller/IndexController.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.controller; 2 | 3 | import com.nowcoder.aspect.LogAspect; 4 | import com.nowcoder.model.User; 5 | import com.nowcoder.service.ToutiaoService; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.beans.factory.annotation.Qualifier; 10 | import org.springframework.boot.Banner; 11 | import org.springframework.http.HttpStatus; 12 | import org.springframework.stereotype.Controller; 13 | import org.springframework.ui.Model; 14 | import org.springframework.web.bind.annotation.*; 15 | import org.springframework.web.servlet.view.RedirectView; 16 | 17 | import javax.servlet.http.Cookie; 18 | import javax.servlet.http.HttpServletRequest; 19 | import javax.servlet.http.HttpServletResponse; 20 | import javax.servlet.http.HttpSession; 21 | import java.util.*; 22 | 23 | /** 24 | * Created by nowcoder on 2016/6/26. 25 | */ 26 | //@Controller 27 | public class IndexController { 28 | private static final Logger logger = LoggerFactory.getLogger(IndexController.class); 29 | 30 | @Autowired 31 | private ToutiaoService toutiaoService; 32 | 33 | @RequestMapping(path = {"/", "/index"}, method = {RequestMethod.GET, RequestMethod.POST}) 34 | @ResponseBody 35 | public String index(HttpSession session) { 36 | logger.info("Visit Index"); 37 | return "Hello NowCoder," + session.getAttribute("msg") 38 | + "
Say:" + toutiaoService.say(); 39 | } 40 | 41 | @RequestMapping(value = {"/profile/{groupId}/{userId}"}) 42 | @ResponseBody 43 | public String profile(@PathVariable("groupId") String groupId, 44 | @PathVariable("userId") int userId, 45 | @RequestParam(value = "type", defaultValue = "1") int type, 46 | @RequestParam(value = "key", defaultValue = "nowcoder") String key) { 47 | return String.format("GID{%s},UID{%d},TYPE{%d},KEY{%s}", groupId, userId, type, key); 48 | } 49 | 50 | @RequestMapping(value = {"/vm"}) 51 | public String news(Model model) { 52 | model.addAttribute("value1", "vv1"); 53 | List colors = Arrays.asList(new String[]{"RED", "GREEN", "BLUE"}); 54 | 55 | Map map = new HashMap(); 56 | for (int i = 0; i < 4; ++i) { 57 | map.put(String.valueOf(i), String.valueOf(i * i)); 58 | } 59 | 60 | model.addAttribute("colors", colors); 61 | model.addAttribute("map", map); 62 | model.addAttribute("user", new User("Jim")); 63 | 64 | return "news"; 65 | } 66 | 67 | @RequestMapping(value = {"/request"}) 68 | @ResponseBody 69 | public String request(HttpServletRequest request, 70 | HttpServletResponse response, 71 | HttpSession session) { 72 | StringBuilder sb = new StringBuilder(); 73 | Enumeration headerNames = request.getHeaderNames(); 74 | while (headerNames.hasMoreElements()) { 75 | String name = headerNames.nextElement(); 76 | sb.append(name + ":" + request.getHeader(name) + "
"); 77 | } 78 | 79 | for (Cookie cookie : request.getCookies()) { 80 | sb.append("Cookie:"); 81 | sb.append(cookie.getName()); 82 | sb.append(":"); 83 | sb.append(cookie.getValue()); 84 | sb.append("
"); 85 | } 86 | 87 | sb.append("getMethod:" + request.getMethod() + "
"); 88 | sb.append("getPathInfo:" + request.getPathInfo() + "
"); 89 | sb.append("getQueryString:" + request.getQueryString() + "
"); 90 | sb.append("getRequestURI:" + request.getRequestURI() + "
"); 91 | 92 | return sb.toString(); 93 | 94 | } 95 | 96 | @RequestMapping(value = {"/response"}) 97 | @ResponseBody 98 | public String response(@CookieValue(value = "nowcoderid", defaultValue = "a") String nowcoderId, 99 | @RequestParam(value = "key", defaultValue = "key") String key, 100 | @RequestParam(value = "value", defaultValue = "value") String value, 101 | HttpServletResponse response) { 102 | response.addCookie(new Cookie(key, value)); 103 | response.addHeader(key, value); 104 | return "NowCoderId From Cookie:" + nowcoderId; 105 | } 106 | 107 | @RequestMapping("/redirect/{code}") 108 | public String redirect(@PathVariable("code") int code, 109 | HttpSession session) { 110 | /* 111 | RedirectView red = new RedirectView("/", true); 112 | if (code == 301) { 113 | red.setStatusCode(HttpStatus.MOVED_PERMANENTLY); 114 | } 115 | return red;*/ 116 | session.setAttribute("msg", "Jump from redirect."); 117 | return "redirect:/"; 118 | } 119 | 120 | @RequestMapping("/admin") 121 | @ResponseBody 122 | public String admin(@RequestParam(value = "key", required = false) String key) { 123 | if ("admin".equals(key)) { 124 | return "hello admin"; 125 | } 126 | throw new IllegalArgumentException("Key 错误"); 127 | } 128 | 129 | @ExceptionHandler() 130 | @ResponseBody 131 | public String error(Exception e) { 132 | return "error:" + e.getMessage(); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/main/resources/static/scripts/main/component/component.js: -------------------------------------------------------------------------------- 1 | (function (window, undefined) { 2 | var Component = Base.createClass('main.component.Component'); 3 | var Event = Base.getClass('main.base.Event'); 4 | $.extend(Component, { 5 | _cIndex: 1, 6 | _domQueue: [], 7 | _tpl: '
', 8 | setEvents: fStaticSetEvents 9 | }); 10 | 11 | $.extend(Component.prototype, Event, { 12 | initialize: fInitialize, 13 | render: fRender, 14 | getEl: fGetEl, 15 | html: fHtml, 16 | getData: fGetData, 17 | // 禁止滚动 18 | forbidScroll: fForbidScroll, 19 | // 重写emit 20 | emit: fEmit, 21 | // 内部方法 22 | _setCustomEvent: _fSetCustomEvent, 23 | _setDomEvent: _fSetDomEvent 24 | }); 25 | 26 | function fStaticSetEvents() { 27 | var that = this; 28 | var aQueue = Component._domQueue; 29 | var oQueue; 30 | while (aQueue.length) { 31 | oQueue = aQueue.shift(); 32 | oQueue._setDomEvent(); 33 | oQueue.emit('render'); 34 | } 35 | } 36 | 37 | function fInitialize(oConf) { 38 | var that = this; 39 | that.rawConfig = oConf; 40 | that.domId = 'jsCpn' + (Component._cIndex++); 41 | that._setCustomEvent(); 42 | Component._domQueue.push(that); 43 | oConf.renderTo && that.render(); 44 | } 45 | 46 | function fRender() { 47 | var that = this; 48 | var oConf = that.rawConfig; 49 | var oRenderTo = $(oConf.renderTo); 50 | var sRenderBy = oConf.renderBy || 'append'; 51 | var oEl = that.getEl(); 52 | oRenderTo[sRenderBy](oEl); 53 | that._setDomEvent(); 54 | that.emit('render'); 55 | } 56 | 57 | function fGetEl() { 58 | var that = this; 59 | if (that.$el) { 60 | return that.$el; 61 | } 62 | var oEl = $('#' + that.domId); 63 | if (oEl.get(0)) { 64 | that.$el = oEl; 65 | return oEl; 66 | } 67 | 68 | var sHtml = that.html(); 69 | that.$el = $(sHtml); 70 | return that.$el; 71 | } 72 | 73 | function fHtml() { 74 | var that = this; 75 | var oConf = that.rawConfig; 76 | var oConstructor = that.constructor; 77 | var sTpl = oConstructor._tpl || Component._tpl; 78 | var oData = that.getData(that.rawConfig); 79 | var sHtml = Base.tpl(sTpl, oData); 80 | // id 和 class 81 | /* jshint ignore:start */ 82 | sHtml = sHtml.replace(/^(\<\w+)([ \>])/, '$1' + ' id="' + that.domId + '"$2'); 83 | /* jshint ignore:end */ 84 | sHtml = sHtml.replace('class="', 'class="' + (oConf.cls || '') + ' '); 85 | return sHtml; 86 | } 87 | 88 | function fGetData(oConf) { 89 | return oConf; 90 | } 91 | 92 | function fForbidScroll(oEl, bForbid) { 93 | $(oEl).css('overflow', bForbid === false ? 'auto' : 'hidden'); 94 | } 95 | 96 | function fEmit(sName) { 97 | var that = this; 98 | if (sName === 'render') { 99 | if (that.rendered) { 100 | return; 101 | } 102 | that.rendered = true; 103 | } 104 | Event.emit.apply(that, arguments); 105 | } 106 | 107 | function _fSetCustomEvent() { 108 | var that = this; 109 | if (that._setedCustomEvent) { 110 | return; 111 | } 112 | that._setedCustomEvent = true; 113 | var oConf = that.rawConfig; 114 | var oConstructor = that.constructor; 115 | $.each(oConstructor.listeners, function (_, oEvent) { 116 | oEvent.type === 'custom' && oEvent.name && oEvent.handler && that.on(oEvent.name, oEvent.handler); 117 | }); 118 | $.each(oConf.listeners, function (sName, fCb) { 119 | Base.isFunction(fCb) && that.on(sName, fCb); 120 | }); 121 | } 122 | 123 | function _fSetDomEvent() { 124 | var that = this; 125 | if (that._setedDomEvent) { 126 | return; 127 | } 128 | that._setedDomEvent = true; 129 | var oConf = that.rawConfig; 130 | var oEl = that.getEl(); 131 | var oConstructor = that.constructor; 132 | // 构造器上的事件 133 | $.each(oConstructor.listeners, function (_, oEvent) { 134 | oEvent.type !== 'custom' && _fBind(oEvent.name, oEvent); 135 | }); 136 | // 配置上面的事件 137 | $.each(oConf.listeners, function (sName, oEvent) { 138 | Base.isObject(oEvent) && _fBind(sName, oEvent); 139 | }); 140 | // 删除dom事件队列 141 | for (var i = Component._domQueue.length - 1; i >= 0; i--) { 142 | if (Component._domQueue[i] === that) { 143 | Component._domQueue.splice(i, 1); 144 | } 145 | } 146 | function _fBind(sName, oEvent) { 147 | var aMatch = sName.match(/^(\S+)\s*(.*)$/); 148 | var sEvent = $.trim(aMatch[1]); 149 | var sSelector = $.trim(aMatch[2]); 150 | var fHandler = oEvent.handler; 151 | if (Base.isFunction(fHandler)) { 152 | if (sSelector) { 153 | oEvent.type === 'bind' && oEl.find(sSelector).on(sEvent, Base.bind(fHandler, that)); 154 | oEvent.type !== 'bind' && oEl.on(sEvent, sSelector, Base.bind(fHandler, that)); 155 | } else { 156 | oEl.on(sEvent, Base.bind(fHandler, that)); 157 | } 158 | } 159 | } 160 | } 161 | 162 | })(window); -------------------------------------------------------------------------------- /src/main/resources/templates/detail.html: -------------------------------------------------------------------------------- 1 | #parse("header.html") 2 |
3 |
4 |
5 | 6 |
7 | #if($like>0) 8 | 9 | #else 10 | 11 | #end 12 | #if($like<0) 13 | 14 | #else 15 | 16 | #end 17 |
18 | 19 |
20 |
21 | 22 |
23 |
24 |

25 | $!{news.title} 26 |

27 |
28 | $!{news.link} 29 | 30 | $!{news.commentCount} 31 | 32 |
33 |
34 |
35 | 50 | 51 | 52 |
53 | 69 | 70 |
71 | #if($user) 72 | 评论 ($!{news.commentCount}) 73 |
74 | 75 | 76 |
77 | 79 | 80 |
81 |
82 | 83 |
84 |
85 | #else 86 | 89 | #end 90 |
91 | 92 |
93 | #foreach($commentvo in $comments) 94 |
95 | 96 | 97 | 98 |
99 |

100 | $date.format('yyyy-MM-dd HH:mm:ss', $!{commentvo.comment.createdDate}) 101 | 102 |

103 |
$!{commentvo.comment.content}
104 |
105 |
106 | #end 107 |
108 | 109 |
110 | 152 | 153 |
154 | #parse("footer.html") -------------------------------------------------------------------------------- /src/main/resources/static/scripts/main/base/base.js: -------------------------------------------------------------------------------- 1 | (function (window, undefined) { 2 | var Base = window.Base = fCreateClass('main.base.Base'); 3 | $.extend(Base, { 4 | ready: fReady, 5 | tpl: fTpl, 6 | bind: fBind, 7 | createClass: fCreateClass, 8 | getClass: fGetClass, 9 | mix: fMix, 10 | inherit: fInherit 11 | }); 12 | 13 | // 类型判断 14 | var aType = ['Array', 'Object', 'Function', 'String', 'Number', 'RegExp']; 15 | for (var i = 0, l = aType.length; i < l; i++) { 16 | (function (sName) { 17 | Base['is' + sName] = function (obj) { 18 | return Object.prototype.toString.call(obj) === '[object ' + sName + ']'; 19 | }; 20 | })(aType[i]); 21 | } 22 | 23 | function fReady(sName, oParam) { 24 | var that = this; 25 | var oSpecialMap = {'document': document, 'body': document.body, 'window': window}; 26 | // 调整参数 27 | if (arguments.length === 1) { 28 | oParam = sName; 29 | sName = 'page' + (new Date().getTime()) + (Math.random()); 30 | } 31 | // 每个页面的脚本作为一个对象处理 32 | var oClass = that.createClass('JSAction.' + sName); 33 | $.extend(oClass, oParam); 34 | $(function () { 35 | oClass.initialize.call(oClass); 36 | // 绑定的事件 37 | $.each(oClass.binds, function (sEventName, sCbName) { 38 | var aMatch = sEventName.match(/^(\S+)\s*(.*)$/); 39 | var sEvent = aMatch[1]; 40 | var sSelector = aMatch[2]; 41 | // 兼容字符串和函数回调 42 | if (that.isString(sCbName)) { 43 | sCbName = oClass[sCbName]; 44 | } 45 | // 绑定事件 46 | $(oSpecialMap[sSelector] || sSelector).on(sEvent, function (oEvent) { 47 | sCbName.call(oClass, oEvent); 48 | }); 49 | }); 50 | // 代理的事件 51 | $.each(oClass.events, function (sEventName, sCbName) { 52 | var aMatch = sEventName.match(/^(\S+)\s*(.*)$/); 53 | var sEvent = aMatch[1]; 54 | var sSelector = aMatch[2]; 55 | // 兼容字符串和函数回调 56 | if (that.isString(sCbName)) { 57 | sCbName = oClass[sCbName]; 58 | } 59 | // 绑定事件 60 | $(document).on(sEvent, sSelector, function (oEvent) { 61 | sCbName.call(oClass, oEvent); 62 | }); 63 | }); 64 | }); 65 | return oClass; 66 | } 67 | 68 | function fTpl(sTpl, oData) { 69 | var that = this; 70 | sTpl = $.trim(sTpl); 71 | return sTpl.replace(/#{(.*?)}/g, function (sStr, sName) { 72 | return oData[sName] === undefined || oData[sName] === null ? '' : oData[sName]; 73 | }); 74 | } 75 | 76 | function fBind(f, oTarget) { 77 | var aArgs = [].slice.call(arguments, 2); 78 | return function () { 79 | var aCallArgs = aArgs.concat([].slice.call(arguments, 0)); 80 | var oResult = f.apply(oTarget, aCallArgs); 81 | aCallArgs.length = 0; 82 | return oResult; 83 | }; 84 | } 85 | 86 | function fCreateClass(sPackage, sClassName) { 87 | var Class = function () { 88 | var that = this.constructor === Class ? this : arguments.callee; 89 | if (that.initialize) { 90 | return that.initialize.apply(that, arguments); 91 | } 92 | }; 93 | if (arguments.length === 0) { 94 | return Class; 95 | } 96 | 97 | var oParent; 98 | if (arguments.length === 2 && typeof sPackage !== 'string') { 99 | oParent = _fFixParent(sPackage); 100 | oParent[sClassName] = Class; 101 | } else { 102 | var sNamespace = sClassName ? (sPackage + '.' + sClassName) : sPackage; 103 | oParent = window; 104 | var aName = sNamespace.split('.'); 105 | for (var i = 0, l = aName.length; i < l; i++) { 106 | var sName = aName[i]; 107 | if (i + 1 === l) { 108 | if (typeof oParent[sName] === 'function') { 109 | Class = oParent[sName]; 110 | } else { 111 | oParent[sName] = Class; 112 | } 113 | } else { 114 | oParent[sName] = _fFixParent(oParent[sName]); 115 | oParent = oParent[sName]; 116 | } 117 | } 118 | } 119 | return Class; 120 | 121 | function _fFixParent(oParent) { 122 | var sType = typeof oParent; 123 | if (sType === 'undefined') { 124 | oParent = {}; 125 | } else if (sType === 'number' || sType === 'string' || sType === 'boolean') { 126 | oParent = new oParent.constructor(oParent); 127 | } 128 | return oParent; 129 | } 130 | } 131 | 132 | function fGetClass(sPackage, sClassName) { 133 | var Class; 134 | try { 135 | var sNamespace = sClassName ? (sPackage + '.' + sClassName) : sPackage; 136 | var aName = sNamespace.split('.'); 137 | var oParent = window; 138 | 139 | for (var i = 0, l = aName.length; i < l; i++) { 140 | var sName = aName[i]; 141 | if (i + 1 === l) { 142 | Class = oParent[sName]; 143 | } else { 144 | oParent = oParent[sName]; 145 | } 146 | } 147 | if (!Class) { 148 | throw new Error('找不到类:' + sNamespace); 149 | } 150 | return Class; 151 | } catch (e) { 152 | throw e; 153 | } 154 | } 155 | 156 | function fMix(oChild, oParent, oExtend, oExtendPrototype) { 157 | var that = this; 158 | if (!oChild || !oParent) { 159 | return; 160 | } 161 | oChild.superClass = oChild.superClass || {}; 162 | $.each(oParent, function (sKey, oVal) { 163 | if (that.isFunction(oVal)) { 164 | if (!oChild.superClass[sKey]) { 165 | oChild.superClass[sKey] = oVal; 166 | } else { 167 | /* jshint ignore:start */ 168 | var _function = oChild.superClass[sKey]; 169 | oChild.superClass[sKey] = function (_property, fFunc) { 170 | return function () { 171 | fFunc.apply(this, arguments); 172 | oParent[_property].apply(this, arguments); 173 | }; 174 | }(sKey, _function); 175 | /* jshint ignore:end */ 176 | } 177 | } else { 178 | oChild.superClass[sKey] = oVal; 179 | } 180 | oChild[sKey] = oChild[sKey] || oVal; 181 | }); 182 | 183 | oExtend && $.extend(oChild, oExtend); 184 | if (oParent.toString != oParent.constructor.prototype.toString) { 185 | oChild.superClass.toString = function () { 186 | oParent.toString.apply(oChild, arguments); 187 | }; 188 | } 189 | oExtendPrototype && oChild.prototype && oParent.prototype && that.inherit(oChild, oParent, oExtendPrototype); 190 | return oChild; 191 | } 192 | 193 | function fInherit(oChild, oParent, oExtend) { 194 | var Inheritance = function() {}; 195 | Inheritance.prototype = oParent.prototype; 196 | oChild.prototype = new Inheritance(); 197 | oChild.prototype.constructor = oChild; 198 | oChild.superConstructor = oParent; 199 | oChild.superClass = oParent.prototype; 200 | oParent._onInherit && oParent._onInherit(oChild); 201 | oExtend && $.extend(oChild.prototype, oExtend); 202 | } 203 | 204 | })(window); -------------------------------------------------------------------------------- /src/main/resources/static/scripts/main/component/popupLogin.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var PopupLogin = Base.createClass('main.component.PopupLogin'); 3 | var Popup = Base.getClass('main.component.Popup'); 4 | var Component = Base.getClass('main.component.Component'); 5 | var Util = Base.getClass('main.base.Util'); 6 | 7 | Base.mix(PopupLogin, Component, { 8 | _tpl: [ 9 | '
', 10 | '
', 11 | '
', 12 | '', 13 | '
', 14 | '
', 15 | '
', 16 | '', 17 | '
', 18 | '
', 19 | '
', 20 | '
', 21 | '', 22 | '
', 23 | '
', 24 | '
', 25 | '', 29 | '
', 30 | '
', 31 | '
'].join(''), 32 | listeners: [{ 33 | name: 'render', 34 | type: 'custom', 35 | handler: function () { 36 | var that = this; 37 | var oEl = that.getEl(); 38 | that.emailIpt = oEl.find('div.js-email'); 39 | that.pwdIpt = oEl.find('div.js-pwd'); 40 | that.initCpn(); 41 | } 42 | }, { 43 | name: 'click a.js-login', 44 | handler: function (oEvent) { 45 | oEvent.preventDefault(); 46 | var that = this; 47 | // 值检查 48 | if (!that.checkVal()) { 49 | return; 50 | } 51 | var oData = that.val(); 52 | $.ajax({ 53 | url: '/login/', 54 | type: 'post', 55 | dataType: 'json', 56 | data: { 57 | username: oData.email, 58 | password: oData.pwd, 59 | rember: oData.rember ? 1 : 0 60 | } 61 | }).done(function (oResult) { 62 | if (oResult.code === 0) { 63 | // window.location.reload(); 64 | that.emit('login'); 65 | } else { 66 | oResult.msgname && that.iptError(that.emailIpt, oResult.msgname); 67 | oResult.msgpwd && that.iptError(that.pwdIpt, oResult.msgpwd); 68 | } 69 | }).fail(function () { 70 | alert('出现错误,请重试'); 71 | }); 72 | } 73 | }, { 74 | name: 'click a.js-register', 75 | handler: function (oEvent) { 76 | oEvent.preventDefault(); 77 | var that = this; 78 | // 值检查 79 | if (!that.checkVal()) { 80 | return; 81 | } 82 | var oData = that.val(); 83 | $.ajax({ 84 | url: '/reg/', 85 | type: 'post', 86 | dataType: 'json', 87 | data: { 88 | username: oData.email, 89 | password: oData.pwd 90 | } 91 | }).done(function (oResult) { 92 | if (oResult.code === 0) { 93 | // window.location.reload(); 94 | that.emit('register'); 95 | } else { 96 | oResult.msgname && that.iptError(that.emailIpt, oResult.msgname); 97 | oResult.msgpwd && that.iptError(that.pwdIpt, oResult.msgpwd); 98 | } 99 | }).fail(function () { 100 | alert('出现错误,请重试'); 101 | }); 102 | } 103 | }], 104 | show: fStaticShow 105 | }, { 106 | initialize: fInitialize, 107 | initCpn: fInitCpn, 108 | val: fVal, 109 | checkVal: fCheckVal, 110 | iptSucc: fIptSucc, 111 | iptError: fIptError, 112 | iptNone: fIptNone 113 | }); 114 | 115 | function fStaticShow(oConf) { 116 | var that = this; 117 | var oLogin = new PopupLogin(oConf); 118 | var oPopup = new Popup({ 119 | width: 540, 120 | content: oLogin.html() 121 | }); 122 | oLogin._popup = oPopup; 123 | Component.setEvents(); 124 | } 125 | 126 | function fInitialize(oConf) { 127 | var that = this; 128 | delete oConf.renderTo; 129 | PopupLogin.superClass.initialize.apply(that, arguments); 130 | } 131 | 132 | function fInitCpn() { 133 | var that = this; 134 | that.emailIpt.find('input').on('focus', Base.bind(that.iptNone, that, that.emailIpt)); 135 | that.pwdIpt.find('input').on('focus', Base.bind(that.iptNone, that, that.pwdIpt)); 136 | } 137 | 138 | function fVal(oData) { 139 | var that = this; 140 | var oEl = that.getEl(); 141 | var oEmailIpt = that.emailIpt.find('input'); 142 | var oPwdIpt = that.pwdIpt.find('input'); 143 | var oRemberChk = oEl.find('.js-rember'); 144 | if (arguments.length === 0) { 145 | return { 146 | email: $.trim(oEmailIpt.val()), 147 | pwd: $.trim(oPwdIpt.val()), 148 | rember: oRemberChk.prop('checked') 149 | }; 150 | } else { 151 | oEmailIpt.val($.trim(oData.email)); 152 | oPwdIpt.val($.trim(oData.pwd)); 153 | oRemberChk.prop('checked', !!oData.rember); 154 | } 155 | } 156 | 157 | function fCheckVal() { 158 | var that = this; 159 | var oData = that.val(); 160 | var bRight = true; 161 | /* 162 | if (!Util.isEmail(oData.email)) { 163 | that.iptError(that.emailIpt, '请填写正确的邮箱'); 164 | bRight = false; 165 | }*/ 166 | if (!oData.pwd) { 167 | that.iptError(that.pwdIpt, '密码不能为空'); 168 | bRight = false; 169 | } else if (oData.pwd.length < 6) { 170 | that.iptError(that.pwdIpt, '密码不能小于6位'); 171 | bRight = false; 172 | } 173 | return bRight; 174 | } 175 | 176 | function fIptSucc(oIpt) { 177 | var that = this; 178 | oIpt = $(oIpt); 179 | that.iptNone(oIpt); 180 | oIpt.addClass('success'); 181 | if (!oIpt.find('.icon-ok-sign').get(0)) { 182 | oIpt.append(''); 183 | } 184 | } 185 | 186 | function fIptError(oIpt, sMsg) { 187 | var that = this; 188 | oIpt = $(oIpt); 189 | that.iptNone(oIpt); 190 | oIpt.addClass('error'); 191 | if (!oIpt.find('.icon-remove-sign').get(0)) { 192 | oIpt.append(''); 193 | } 194 | var oSpan = oIpt.find('.input-tip'); 195 | if (!oSpan.get(0)) { 196 | oSpan = $(''); 197 | oIpt.append(oSpan); 198 | } 199 | oSpan.html($.trim(sMsg)); 200 | } 201 | 202 | function fIptNone(oIpt) { 203 | var that = this; 204 | $(oIpt).removeClass('error success'); 205 | } 206 | })(window); -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/controller/NewsController.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.controller; 2 | 3 | import com.nowcoder.model.*; 4 | import com.nowcoder.service.*; 5 | import com.nowcoder.util.ToutiaoUtil; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Controller; 10 | import org.springframework.ui.Model; 11 | import org.springframework.util.StreamUtils; 12 | import org.springframework.web.bind.annotation.*; 13 | import org.springframework.web.multipart.MultipartFile; 14 | 15 | import javax.servlet.http.HttpServletResponse; 16 | import java.io.File; 17 | import java.io.FileInputStream; 18 | import java.io.IOException; 19 | import java.util.ArrayList; 20 | import java.util.Date; 21 | import java.util.List; 22 | 23 | /** 24 | * Created by 周杰伦 on 2018/5/5. 25 | */ 26 | @Controller 27 | public class NewsController { 28 | private static final Logger logger = LoggerFactory.getLogger(NewsController.class); 29 | 30 | @Autowired 31 | private NewsService newsService; 32 | 33 | @Autowired 34 | private AliService aliService; 35 | 36 | @Autowired 37 | private QiniuService qiniuService; 38 | 39 | @Autowired 40 | private HostHolder hostHolder; 41 | 42 | @Autowired 43 | private CommentService commentService; 44 | 45 | @Autowired 46 | private UserService userService; 47 | 48 | @Autowired 49 | private LikeService likeService; 50 | 51 | @RequestMapping(path = {"/news/{newsId}"}, method = {RequestMethod.GET}) 52 | public String newsDetail(@PathVariable("newsId") int newsId, Model model) { 53 | News news = newsService.getById(newsId); 54 | if (news != null) { 55 | int localUserId = hostHolder.getUser() != null ? hostHolder.getUser().getId() : 0; 56 | if (localUserId != 0) { 57 | model.addAttribute("like", likeService.getLikeStatus(localUserId, EntityType.ENTITY_NEWS, news.getId())); 58 | } else { 59 | model.addAttribute("like", 0); 60 | } 61 | // 评论 62 | List comments = commentService.getCommentsByEntity(news.getId(), EntityType.ENTITY_NEWS); 63 | List commentVOs = new ArrayList(); 64 | for (Comment comment : comments) { 65 | ViewObject vo = new ViewObject(); 66 | vo.set("comment", comment); 67 | vo.set("user", userService.getUser(comment.getUserId())); 68 | commentVOs.add(vo); 69 | } 70 | model.addAttribute("comments", commentVOs); 71 | } 72 | model.addAttribute("news", news); 73 | model.addAttribute("owner", userService.getUser(news.getUserId())); 74 | return "detail"; 75 | } 76 | 77 | @RequestMapping(path = {"/user/addNews/"}, method = {RequestMethod.POST}) 78 | @ResponseBody 79 | public String addNews(@RequestParam("image") String image, 80 | @RequestParam("title") String title, 81 | @RequestParam("link") String link) { 82 | try { 83 | News news = new News(); 84 | news.setCreatedDate(new Date()); 85 | news.setTitle(title); 86 | news.setImage(image); 87 | news.setLink(link); 88 | if (hostHolder.getUser() != null) { 89 | news.setUserId(hostHolder.getUser().getId()); 90 | } else { 91 | // 设置一个匿名用户 92 | news.setUserId(3); 93 | } 94 | newsService.addNews(news); 95 | return ToutiaoUtil.getJSONString(0); 96 | } catch (Exception e) { 97 | logger.error("添加资讯失败" + e.getMessage()); 98 | return ToutiaoUtil.getJSONString(1, "发布失败"); 99 | } 100 | } 101 | 102 | @RequestMapping(path = {"/addComment"}, method = {RequestMethod.POST}) 103 | public String addComment(@RequestParam("newsId") int newsId, 104 | @RequestParam("content") String content) { 105 | try { 106 | Comment comment = new Comment(); 107 | comment.setUserId(hostHolder.getUser().getId()); 108 | comment.setContent(content); 109 | comment.setEntityType(EntityType.ENTITY_NEWS); 110 | comment.setEntityId(newsId); 111 | comment.setCreatedDate(new Date()); 112 | comment.setStatus(0); 113 | commentService.addComment(comment); 114 | 115 | // 更新评论数量,以后用异步实现 116 | int count = commentService.getCommentCount(comment.getEntityId(), comment.getEntityType()); 117 | newsService.updateCommentCount(comment.getEntityId(), count); 118 | 119 | } catch (Exception e) { 120 | logger.error("提交评论错误" + e.getMessage()); 121 | } 122 | return "redirect:/news/" + String.valueOf(newsId); 123 | } 124 | 125 | @RequestMapping("/uploadImage/") 126 | @ResponseBody 127 | //这里使用阿里oss,有空把七牛云搞一下 128 | public String uploadOSS(@RequestParam("file") MultipartFile file) { 129 | try { 130 | String fileUrl = aliService.saveImage(file); 131 | if (fileUrl == null) { 132 | //先错误后正确 133 | return ToutiaoUtil.getJSONString(1, "上传图片失败"); 134 | } 135 | return ToutiaoUtil.getJSONString(0, fileUrl); 136 | } catch (IOException e) { 137 | logger.error("上传失败" + e.getMessage()); 138 | return ToutiaoUtil.getJSONString(1, "上传失败"); 139 | } 140 | } 141 | 142 | @RequestMapping("/uploadQiniu/") 143 | @ResponseBody 144 | //这里使用阿里oss,有空把七牛云搞一下 145 | public String uploadQiniu(@RequestParam("file") MultipartFile file) { 146 | try { 147 | String fileUrl = qiniuService.saveImage(file); 148 | if (fileUrl == null) { 149 | //先错误后正确 150 | return ToutiaoUtil.getJSONString(1, "上传图片失败"); 151 | } 152 | return ToutiaoUtil.getJSONString(0, fileUrl); 153 | } catch (IOException e) { 154 | logger.error("上传失败" + e.getMessage()); 155 | return ToutiaoUtil.getJSONString(1, "上传失败"); 156 | } 157 | } 158 | 159 | // @RequestMapping("/uploadImage/") 160 | // @ResponseBody 161 | // public String uploadImage(@RequestParam("file") MultipartFile file) { 162 | // try { 163 | // String fileUrl = newsService.saveImage(file); 164 | // if (fileUrl == null) { 165 | // //先错误后正确 166 | // return ToutiaoUtil.getJSONString(1, "上传图片失败"); 167 | // } 168 | // return ToutiaoUtil.getJSONString(0, fileUrl); 169 | // } catch (IOException e) { 170 | // logger.error("上传失败" + e.getMessage()); 171 | // return ToutiaoUtil.getJSONString(1, "上传失败"); 172 | // } 173 | // } 174 | // 175 | // @RequestMapping("/uploadImages/") 176 | // @ResponseBody 177 | // public String uploadImages(@RequestParam("files") MultipartFile[] files) { 178 | // try { 179 | // String fileUrl = newsService.saveImages(files); 180 | // if (fileUrl == null) { 181 | // //先错误后正确 182 | // return ToutiaoUtil.getJSONString(1, "上传图片失败"); 183 | // } 184 | // return ToutiaoUtil.getJSONString(0, fileUrl); 185 | // } catch (IOException e) { 186 | // logger.error("上传失败" + e.getMessage()); 187 | // return ToutiaoUtil.getJSONString(1, "上传失败"); 188 | // } 189 | // } 190 | 191 | @RequestMapping(path = {"/image/"}, method = {RequestMethod.GET}) 192 | @ResponseBody 193 | public void getImage(@RequestParam("name") String imageName, 194 | HttpServletResponse response) { 195 | try { 196 | response.setContentType("image/jpg"); 197 | StreamUtils.copy(new FileInputStream(new 198 | File(ToutiaoUtil.IMAGE_DIR + imageName)), response.getOutputStream()); 199 | } catch (Exception e) { 200 | logger.error("读取图片错误" + imageName + e.getMessage()); 201 | } 202 | } 203 | 204 | } 205 | -------------------------------------------------------------------------------- /src/test/java/com/nowcoder/MultiThread.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder; 2 | 3 | import java.util.Random; 4 | import java.util.concurrent.*; 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | 7 | /** 8 | * Created by nowcoder on 2016/7/23. 9 | */ 10 | 11 | class MyThread extends Thread { 12 | private int tid; 13 | public MyThread(int tid) { 14 | this.tid = tid; 15 | } 16 | 17 | @Override 18 | public void run() { 19 | try { 20 | for (int i = 0; i < 10; ++i) { 21 | Thread.sleep(1000); 22 | System.out.println(String.format("T%d:%d", tid, i)); 23 | } 24 | } catch (Exception e) { 25 | e.printStackTrace(); 26 | } 27 | } 28 | } 29 | 30 | class Producer implements Runnable { 31 | private BlockingQueue q; 32 | 33 | public Producer(BlockingQueue q) { 34 | this.q = q; 35 | } 36 | @Override 37 | public void run() { 38 | try { 39 | for (int i = 0; i < 100; ++i) { 40 | Thread.sleep(10); 41 | q.put(String.valueOf(i)); 42 | } 43 | 44 | } catch (Exception e) { 45 | e.printStackTrace(); 46 | } 47 | } 48 | } 49 | 50 | class Consumer implements Runnable { 51 | private BlockingQueue q; 52 | 53 | public Consumer(BlockingQueue q) { 54 | this.q = q; 55 | } 56 | @Override 57 | public void run() { 58 | try { 59 | while (true) { 60 | System.out.println(Thread.currentThread().getName() + ":" + q.take()); 61 | } 62 | } catch (Exception e) { 63 | e.printStackTrace(); 64 | } 65 | } 66 | } 67 | 68 | public class MultiThread { 69 | public static void testThread() { 70 | for (int i = 0; i < 10; ++i) { 71 | //new MyThread(i).start(); 72 | } 73 | 74 | for (int i = 0; i < 10; ++i) { 75 | final int tid = i; 76 | new Thread(new Runnable() { 77 | @Override 78 | public void run() { 79 | try { 80 | for (int i = 0; i < 10; ++i) { 81 | Thread.sleep(1000); 82 | System.out.println(String.format("T2%d:%d", tid, i)); 83 | } 84 | } catch (Exception e) { 85 | e.printStackTrace(); 86 | } 87 | } 88 | }).start(); 89 | } 90 | } 91 | 92 | private static Object obj = new Object(); 93 | 94 | public static void testSynchronized1() { 95 | synchronized (obj) { 96 | try { 97 | for (int i = 0; i < 10; ++i) { 98 | Thread.sleep(1000); 99 | System.out.println(String.format("T3%d", i)); 100 | } 101 | } catch (Exception e) { 102 | e.printStackTrace(); 103 | } 104 | } 105 | } 106 | 107 | public static void testSynchronized2() { 108 | synchronized (new Object()) { 109 | try { 110 | for (int i = 0; i < 10; ++i) { 111 | Thread.sleep(1000); 112 | System.out.println(String.format("T4%d", i)); 113 | } 114 | } catch (Exception e) { 115 | e.printStackTrace(); 116 | } 117 | } 118 | } 119 | 120 | public static void testSynchronized() { 121 | for (int i = 0; i < 10; ++i) { 122 | new Thread(new Runnable() { 123 | @Override 124 | public void run() { 125 | testSynchronized1(); 126 | testSynchronized2(); 127 | } 128 | }).start(); 129 | } 130 | } 131 | 132 | public static void testBlockingQueue() { 133 | BlockingQueue q = new ArrayBlockingQueue(10); 134 | new Thread(new Producer(q)).start(); 135 | new Thread(new Consumer(q), "Consumer1").start(); 136 | new Thread(new Consumer(q), "Consumer2").start(); 137 | } 138 | 139 | private static int counter = 0; 140 | private static AtomicInteger atomicInteger = new AtomicInteger(0); 141 | public static void sleep(int mills) { 142 | try { 143 | //Thread.sleep(new Random().nextInt(mills)); 144 | Thread.sleep(mills); 145 | } catch (Exception e) { 146 | e.printStackTrace(); 147 | } 148 | } 149 | public static void testWithAtomic() { 150 | for (int i = 0; i < 10; ++i) { 151 | new Thread(new Runnable() { 152 | @Override 153 | public void run() { 154 | sleep(1000); 155 | for (int j = 0; j < 10; ++j) { 156 | System.out.println(atomicInteger.incrementAndGet()); 157 | } 158 | } 159 | }).start(); 160 | } 161 | } 162 | 163 | public static void testWithoutAtomic() { 164 | for (int i = 0; i < 10; ++i) { 165 | new Thread(new Runnable() { 166 | @Override 167 | public void run() { 168 | sleep(1000); 169 | for (int j = 0; j < 10; ++j) { 170 | counter++; 171 | System.out.println(counter); 172 | } 173 | } 174 | }).start(); 175 | } 176 | } 177 | 178 | public static void testAtomic() { 179 | testWithAtomic(); 180 | testWithoutAtomic(); 181 | } 182 | 183 | private static ThreadLocal threadLocalUserIds = new ThreadLocal<>(); 184 | private static int userId; 185 | 186 | public static void testThreadLocal() { 187 | for (int i = 0; i < 10; ++i) { 188 | final int finalI = i; 189 | new Thread(new Runnable() { 190 | @Override 191 | public void run() { 192 | threadLocalUserIds.set(finalI); 193 | sleep(1000); 194 | System.out.println("ThreadLocal: " + threadLocalUserIds.get()); 195 | } 196 | }).start(); 197 | } 198 | 199 | for (int i = 0; i < 10; ++i) { 200 | final int finalI = i; 201 | new Thread(new Runnable() { 202 | @Override 203 | public void run() { 204 | userId = finalI; 205 | sleep(1000); 206 | System.out.println("NonThreadLocal: " + userId); 207 | } 208 | }).start(); 209 | } 210 | } 211 | 212 | public static void testExecutor() { 213 | //ExecutorService service = Executors.newSingleThreadExecutor(); 214 | ExecutorService service = Executors.newFixedThreadPool(2); 215 | service.submit(new Runnable() { 216 | @Override 217 | public void run() { 218 | for (int i = 0; i < 10; ++i) { 219 | sleep(1000); 220 | System.out.println("Execute1 " + i); 221 | } 222 | } 223 | }); 224 | 225 | service.submit(new Runnable() { 226 | @Override 227 | public void run() { 228 | for (int i = 0; i < 10; ++i) { 229 | sleep(1000); 230 | System.out.println("Execute2 " + i); 231 | } 232 | } 233 | }); 234 | 235 | service.shutdown(); 236 | while (!service.isTerminated()) { 237 | sleep(1000); 238 | System.out.println("Wait for termination."); 239 | } 240 | } 241 | 242 | public static void testFutrue() { 243 | ExecutorService service = Executors.newSingleThreadExecutor(); 244 | Future future = service.submit(new Callable() { 245 | @Override 246 | public Integer call() throws Exception { 247 | sleep(1000); 248 | return 1; 249 | //throw new IllegalArgumentException("异常"); 250 | } 251 | }); 252 | 253 | service.shutdown(); 254 | 255 | try { 256 | //System.out.println(future.get()); 257 | System.out.println(future.get(100, TimeUnit.MILLISECONDS)); 258 | } catch (Exception e) { 259 | e.printStackTrace(); 260 | } 261 | } 262 | 263 | 264 | public static void main(String[] argv) { 265 | //testThread(); 266 | //testSynchronized(); 267 | //testBlockingQueue(); 268 | //testAtomic(); 269 | //testThreadLocal(); 270 | //testExecutor(); 271 | testFutrue(); 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /src/main/resources/templates/home.html: -------------------------------------------------------------------------------- 1 | #parse("header.html") 2 | 3 |
4 | 60 | 61 |
62 |
63 |
64 | 65 | #set($cur_date = '') 66 | #foreach($vo in $vos) 67 | #if ($cur_date != $date.format('yyyy-MM-dd', $vo.news.createdDate)) 68 | #if ($foreach.index > 0) 69 |
## 上一个要收尾 70 | #end 71 | #set($cur_date = $date.format('yyyy-MM-dd', $vo.news.createdDate)) 72 |

73 | 74 | 头条资讯   $date.format('yyyy-MM-dd', $vo.news.createdDate) 75 |

76 | 77 |
78 | #end 79 |
80 |
81 | #if ($vo.like > 0) 82 | 83 | #else 84 | 85 | #end 86 | #if($vo.like < 0) 87 | 88 | #else 89 | 90 | #end 91 |
92 |
93 |
94 | 95 |
96 |
97 |

98 | $!{vo.news.title} 99 |

100 |
101 | $!{vo.news.link} 102 | 103 | $!{vo.news.commentCount} 104 | 105 |
106 |
107 |
108 | 124 | 125 | 126 |
127 | 128 | 133 | #if ($foreach.count == $vos.size()) ##最后有个元素要收尾 134 |
135 | #end 136 | 137 | #end 138 | 139 | 140 |
141 |
142 |
143 | 144 |
145 | 146 | 147 | #if ($pop) 148 | 151 | 152 | #end 153 | 154 | #parse("footer.html") -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/util/JedisAdapter.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.util; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.InitializingBean; 7 | import org.springframework.stereotype.Service; 8 | import redis.clients.jedis.BinaryClient; 9 | import redis.clients.jedis.Jedis; 10 | import redis.clients.jedis.JedisPool; 11 | import redis.clients.jedis.Tuple; 12 | 13 | 14 | import java.util.List; 15 | 16 | /** 17 | * Created by nowcoder on 2016/7/13. 18 | */ 19 | @Service 20 | public class JedisAdapter implements InitializingBean { 21 | private static final Logger logger = LoggerFactory.getLogger(JedisAdapter.class); 22 | 23 | 24 | public static void print(int index, Object obj) { 25 | System.out.println(String.format("%d,%s", index, obj.toString())); 26 | } 27 | 28 | // public static void main(String[] args) { 29 | // Jedis jedis = new Jedis(); 30 | // jedis.flushAll(); 31 | // // get,set 32 | // jedis.set("hello", "world"); 33 | // print(1, jedis.get("hello")); 34 | // jedis.rename("hello", "newhello"); 35 | // print(1, jedis.get("newhello")); 36 | // jedis.setex("hello2", 15, "world"); 37 | // 38 | // // 数值操作 39 | // jedis.set("pv", "100"); 40 | // jedis.incr("pv"); 41 | // jedis.decrBy("pv", 5); 42 | // print(2, jedis.get("pv")); 43 | // print(3, jedis.keys("*")); 44 | // 45 | // // 列表操作, 最近来访, 粉丝列表,消息队列 46 | // String listName = "list"; 47 | // jedis.del(listName); 48 | // for (int i = 0; i < 10; ++i) { 49 | // jedis.lpush(listName, "a" + String.valueOf(i)); 50 | // } 51 | // print(4, jedis.lrange(listName, 0, 12)); // 最近来访10个id 52 | // print(5, jedis.llen(listName)); 53 | // print(6, jedis.lpop(listName)); 54 | // print(7, jedis.llen(listName)); 55 | // print(8, jedis.lrange(listName, 2, 6)); // 最近来访10个id 56 | // print(9, jedis.lindex(listName, 3)); 57 | // print(10, jedis.linsert(listName, BinaryClient.LIST_POSITION.AFTER, "a4", "xx")); 58 | // print(10, jedis.linsert(listName, BinaryClient.LIST_POSITION.BEFORE, "a4", "bb")); 59 | // print(11, jedis.lrange(listName, 0, 12)); 60 | // 61 | // 62 | // // hash, 可变字段 63 | // String userKey = "userxx"; 64 | // jedis.hset(userKey, "name", "jim"); 65 | // jedis.hset(userKey, "age", "12"); 66 | // jedis.hset(userKey, "phone", "18666666666"); 67 | // print(12, jedis.hget(userKey, "name")); 68 | // print(13, jedis.hgetAll(userKey)); 69 | // jedis.hdel(userKey, "phone"); 70 | // print(14, jedis.hgetAll(userKey)); 71 | // print(15, jedis.hexists(userKey, "email")); 72 | // print(16, jedis.hexists(userKey, "age")); 73 | // print(17, jedis.hkeys(userKey)); 74 | // print(18, jedis.hvals(userKey)); 75 | // jedis.hsetnx(userKey, "school", "zju"); 76 | // jedis.hsetnx(userKey, "name", "yxy"); 77 | // print(19, jedis.hgetAll(userKey)); 78 | // 79 | // // 集合,点赞用户群, 共同好友 80 | // String likeKey1 = "newsLike1"; 81 | // String likeKey2 = "newsLike2"; 82 | // for (int i = 0; i < 10; ++i) { 83 | // jedis.sadd(likeKey1, String.valueOf(i)); 84 | // jedis.sadd(likeKey2, String.valueOf(i * 2)); 85 | // } 86 | // print(20, jedis.smembers(likeKey1)); 87 | // print(21, jedis.smembers(likeKey2)); 88 | // print(22, jedis.sunion(likeKey1, likeKey2)); 89 | // print(23, jedis.sdiff(likeKey1, likeKey2)); 90 | // print(24, jedis.sinter(likeKey1, likeKey2)); 91 | // print(25, jedis.sismember(likeKey1, "12")); 92 | // print(26, jedis.sismember(likeKey2, "12")); 93 | // jedis.srem(likeKey1, "5"); 94 | // print(27, jedis.smembers(likeKey1)); 95 | // // 从1移动到2 96 | // jedis.smove(likeKey2, likeKey1, "14"); 97 | // print(28, jedis.smembers(likeKey1)); 98 | // print(29, jedis.scard(likeKey1)); 99 | // 100 | // // 排序集合,有限队列,排行榜 101 | // String rankKey = "rankKey"; 102 | // jedis.zadd(rankKey, 15, "Jim"); 103 | // jedis.zadd(rankKey, 60, "Ben"); 104 | // jedis.zadd(rankKey, 90, "Lee"); 105 | // jedis.zadd(rankKey, 75, "Lucy"); 106 | // jedis.zadd(rankKey, 80, "Mei"); 107 | // print(30, jedis.zcard(rankKey)); 108 | // print(31, jedis.zcount(rankKey, 61, 100)); 109 | // // 改错卷了 110 | // print(32, jedis.zscore(rankKey, "Lucy")); 111 | // jedis.zincrby(rankKey, 2, "Lucy"); 112 | // print(33, jedis.zscore(rankKey, "Lucy")); 113 | // jedis.zincrby(rankKey, 2, "Luc"); 114 | // print(34, jedis.zscore(rankKey, "Luc")); 115 | // print(35, jedis.zcount(rankKey, 0, 100)); 116 | // // 1-4 名 Luc 117 | // print(36, jedis.zrange(rankKey, 0, 10)); 118 | // print(36, jedis.zrange(rankKey, 1, 3)); 119 | // print(36, jedis.zrevrange(rankKey, 1, 3)); 120 | // for (Tuple tuple : jedis.zrangeByScoreWithScores(rankKey, "60", "100")) { 121 | // print(37, tuple.getElement() + ":" + String.valueOf(tuple.getScore())); 122 | // } 123 | // 124 | // print(38, jedis.zrank(rankKey, "Ben")); 125 | // print(39, jedis.zrevrank(rankKey, "Ben")); 126 | // 127 | // String setKey = "zset"; 128 | // jedis.zadd(setKey, 1, "a"); 129 | // jedis.zadd(setKey, 1, "b"); 130 | // jedis.zadd(setKey, 1, "c"); 131 | // jedis.zadd(setKey, 1, "d"); 132 | // jedis.zadd(setKey, 1, "e"); 133 | // print(40, jedis.zlexcount(setKey, "-", "+")); 134 | // print(41, jedis.zlexcount(setKey, "(b", "[d")); 135 | // print(42, jedis.zlexcount(setKey, "[b", "[d")); 136 | // jedis.zrem(setKey, "b"); 137 | // print(43, jedis.zrange(setKey, 0, 10)); 138 | // jedis.zremrangeByLex(setKey, "(c", "+"); 139 | // print(44, jedis.zrange(setKey, 0, 2)); 140 | // 141 | // /* 142 | // jedis.lpush("aaa", "A"); 143 | // jedis.lpush("aaa", "B"); 144 | // jedis.lpush("aaa", "C"); 145 | // print(45, jedis.brpop(0, "aaa")); 146 | // print(45, jedis.brpop(0, "aaa")); 147 | // print(45, jedis.brpop(0, "aaa")); 148 | // */ 149 | // 150 | // 151 | // JedisPool pool = new JedisPool(); 152 | // for (int i = 0; i < 100; ++i) { 153 | // Jedis j = pool.getResource(); 154 | // j.get("a"); 155 | // j.close(); 156 | // } 157 | // } 158 | 159 | private Jedis jedis = null; 160 | private JedisPool pool = null; 161 | 162 | @Override 163 | public void afterPropertiesSet() throws Exception { 164 | //jedis = new Jedis("localhost"); 165 | pool = new JedisPool("localhost", 6379); 166 | } 167 | 168 | private Jedis getJedis() { 169 | //return jedis; 170 | return pool.getResource(); 171 | } 172 | 173 | public String get(String key) { 174 | Jedis jedis = null; 175 | try { 176 | jedis = pool.getResource(); 177 | return getJedis().get(key); 178 | } catch (Exception e) { 179 | logger.error("发生异常" + e.getMessage()); 180 | return null; 181 | } finally { 182 | if (jedis != null) { 183 | jedis.close(); 184 | } 185 | } 186 | } 187 | 188 | public void set(String key, String value) { 189 | Jedis jedis = null; 190 | try { 191 | jedis = pool.getResource(); 192 | jedis.set(key, value); 193 | } catch (Exception e) { 194 | logger.error("发生异常" + e.getMessage()); 195 | } finally { 196 | if (jedis != null) { 197 | jedis.close(); 198 | } 199 | } 200 | } 201 | 202 | public long sadd(String key, String value) { 203 | Jedis jedis = null; 204 | try { 205 | jedis = pool.getResource(); 206 | return jedis.sadd(key, value); 207 | } catch (Exception e) { 208 | logger.error("发生异常" + e.getMessage()); 209 | return 0; 210 | } finally { 211 | if (jedis != null) { 212 | jedis.close(); 213 | } 214 | } 215 | } 216 | 217 | public long srem(String key, String value) { 218 | Jedis jedis = null; 219 | try { 220 | jedis = pool.getResource(); 221 | return jedis.srem(key, value); 222 | } catch (Exception e) { 223 | logger.error("发生异常" + e.getMessage()); 224 | return 0; 225 | } finally { 226 | if (jedis != null) { 227 | jedis.close(); 228 | } 229 | } 230 | } 231 | 232 | public boolean sismember(String key, String value) { 233 | Jedis jedis = null; 234 | try { 235 | jedis = pool.getResource(); 236 | return jedis.sismember(key, value); 237 | } catch (Exception e) { 238 | logger.error("发生异常" + e.getMessage()); 239 | return false; 240 | } finally { 241 | if (jedis != null) { 242 | jedis.close(); 243 | } 244 | } 245 | } 246 | 247 | public long scard(String key) { 248 | Jedis jedis = null; 249 | try { 250 | jedis = pool.getResource(); 251 | return jedis.scard(key); 252 | } catch (Exception e) { 253 | logger.error("发生异常" + e.getMessage()); 254 | return 0; 255 | } finally { 256 | if (jedis != null) { 257 | jedis.close(); 258 | } 259 | } 260 | } 261 | 262 | public void setex(String key, String value) { 263 | // 验证码, 防机器注册,记录上次注册时间,有效期3天 264 | Jedis jedis = null; 265 | try { 266 | jedis = pool.getResource(); 267 | jedis.setex(key, 10, value); 268 | } catch (Exception e) { 269 | logger.error("发生异常" + e.getMessage()); 270 | } finally { 271 | if (jedis != null) { 272 | jedis.close(); 273 | } 274 | } 275 | } 276 | 277 | public long lpush(String key, String value) { 278 | Jedis jedis = null; 279 | try { 280 | jedis = pool.getResource(); 281 | return jedis.lpush(key, value); 282 | } catch (Exception e) { 283 | logger.error("发生异常" + e.getMessage()); 284 | return 0; 285 | } finally { 286 | if (jedis != null) { 287 | jedis.close(); 288 | } 289 | } 290 | } 291 | 292 | public List brpop(int timeout, String key) { 293 | Jedis jedis = null; 294 | try { 295 | jedis = pool.getResource(); 296 | return jedis.brpop(timeout, key); 297 | } catch (Exception e) { 298 | logger.error("发生异常" + e.getMessage()); 299 | return null; 300 | } finally { 301 | if (jedis != null) { 302 | jedis.close(); 303 | } 304 | } 305 | } 306 | 307 | public void setObject(String key, Object obj) { 308 | set(key, JSON.toJSONString(obj)); 309 | } 310 | 311 | public T getObject(String key, Class clazz) { 312 | String value = get(key); 313 | if (value != null) { 314 | return JSON.parseObject(value, clazz); 315 | } 316 | return null; 317 | } 318 | } 319 | --------------------------------------------------------------------------------