├── src ├── main │ ├── resources │ │ ├── SensitiveWords.txt │ │ ├── static │ │ │ ├── images │ │ │ │ ├── img │ │ │ │ │ ├── 个人主页.png │ │ │ │ │ ├── 发表问题.png │ │ │ │ │ ├── 注册登录.png │ │ │ │ │ ├── 私信页面.png │ │ │ │ │ ├── 首页.png │ │ │ │ │ ├── 查看问题,评论.png │ │ │ │ │ ├── spinner2.8f60205d.gif │ │ │ │ │ ├── sprites.auto.915a539c.png │ │ │ │ │ ├── sprites-1.9.2.4c54885a.png │ │ │ │ │ ├── sprites@2x.auto.dd5c79c1.png │ │ │ │ │ └── sprites-1.9.2@2x.6e638473.png │ │ │ │ └── res │ │ │ │ │ ├── nk.png │ │ │ │ │ ├── captcha.gif │ │ │ │ │ ├── 102799979_m.jpg │ │ │ │ │ ├── 1ce495b02_m.jpg │ │ │ │ │ ├── 935f87219_m.jpg │ │ │ │ │ ├── a3f80f6a6_m.jpg │ │ │ │ │ ├── aadd7b895_m.jpg │ │ │ │ │ ├── aadd7b895_s.jpg │ │ │ │ │ ├── b144d91ec_m.jpg │ │ │ │ │ ├── c28e9af7f_m.jpg │ │ │ │ │ ├── c79247853_m.jpg │ │ │ │ │ ├── c94c92af8_m.jpg │ │ │ │ │ ├── da8e974dc_m.jpg │ │ │ │ │ ├── da8e974dc_s.jpg │ │ │ │ │ ├── e6f4caaaa_m.jpg │ │ │ │ │ ├── 070a9fb26_is.jpg │ │ │ │ │ ├── 6088e39f2_is.jpg │ │ │ │ │ ├── b85ddd8aa_xl.jpg │ │ │ │ │ ├── da8e974dc_is.jpg │ │ │ │ │ ├── e174d6d0c_is.jpg │ │ │ │ │ ├── hour.3d371c99.png │ │ │ │ │ ├── nacl.656ec1c4.png │ │ │ │ │ ├── weekly.65279d61.png │ │ │ │ │ ├── 0ba3da3f03ced7a438118b0be77df56c_m.jpg │ │ │ │ │ ├── 0c6a39621ab1d456b1e6e492d0becc0c_s.jpg │ │ │ │ │ ├── 0cf21546298ad1ed3cb64be61d822c27_m.jpg │ │ │ │ │ ├── 11be4a90ed938abfbab4899df56ee754_s.png │ │ │ │ │ ├── 12a8e1ce5ad8060fddb93ae2df98028b_m.jpg │ │ │ │ │ ├── 19456ebfe8b207320735f282769ac635_s.jpg │ │ │ │ │ ├── 1ac7840eeb19ada0bbf85f51702d5784_s.jpg │ │ │ │ │ ├── 24ce38dd5fc2a0c4e0525e577eef7d64_s.png │ │ │ │ │ ├── 2e21e58a990f5c756e813a64a4bba14c_m.jpg │ │ │ │ │ ├── 33fb6f51a3f4e16b6e89172040451dca_b.png │ │ │ │ │ ├── 3b673d6335ef6788d1659ee2b6381e97_m.jpg │ │ │ │ │ ├── 41d652d947a489e056b0179ba137294b_m.png │ │ │ │ │ ├── 4d104b6c6a08f7e1a48f4f32c88b1ce2_m.jpg │ │ │ │ │ ├── 51559bbebaa7fd395c271b7b1c8b9f26_m.jpg │ │ │ │ │ ├── 6ceea810748d179f57cac0baa5cf9592_s.jpg │ │ │ │ │ ├── 6cfbfc0c5e3c5ecd8784f7e733a75b4f_m.jpg │ │ │ │ │ ├── 845303838eca4a5b5f03cc3ca994ec28_s.png │ │ │ │ │ ├── 845c492813e72b85c6e11cccf8ed0ff8_m.jpg │ │ │ │ │ ├── a57fdaff90865eaaef2e87051624862b_m.jpg │ │ │ │ │ ├── actioncard-validation@2x.65147043.png │ │ │ │ │ ├── b3aadf89405941b05a5ce00fb06f8281_m.jpg │ │ │ │ │ ├── b4a6228e6810d38e19e491c173af4d5c_m.png │ │ │ │ │ ├── badaaf312e59c125928bd1ea2d4b5a51_b.jpg │ │ │ │ │ ├── bb73b7fb48b0cb63aa573415dfe4d0c5_m.jpg │ │ │ │ │ ├── be39d110759e68f389b7d2934d7353bc_m.jpg │ │ │ │ │ ├── cbc5d3c6f333215a1c480cb3b4735b45_m.jpg │ │ │ │ │ ├── d207854fffc9e0289fbd6bbbb3986988_s.jpg │ │ │ │ │ ├── d822a919d93a761634a67c2022a3f614_m.jpg │ │ │ │ │ ├── df4aa616fdcfbd861c010ff71aaef95c_b.jpg │ │ │ │ │ ├── fa70eff301ba417d4a9d3f55d603a29e_s.png │ │ │ │ │ ├── 10a4cd7fb082375332be33eadfd14c58_is.jpg │ │ │ │ │ ├── 11ba31c8bb5473a44b7690eff24b9123_is.jpg │ │ │ │ │ ├── 272627e471a533f58f319a9e600e0a94_is.png │ │ │ │ │ ├── 4528283ed249589634546327431667bf_is.jpg │ │ │ │ │ ├── 6c76223a5aef2f1d29c680a2524de791_is.jpg │ │ │ │ │ ├── 6fd46860a4b6cbc1e52d676f217ea9fd_is.jpg │ │ │ │ │ ├── 7986ef6045f2cef8352be5affce5f7d1_is.jpg │ │ │ │ │ ├── 837b60aa36029a2309974e74de3b62e9_xl.png │ │ │ │ │ ├── 975baaf73fd76f48ce6f05e19b176878_xl.jpg │ │ │ │ │ ├── a4df63114bd9374a73775e30db1cdd36_is.jpg │ │ │ │ │ ├── actioncard-suggested-avatar.c5af416d.png │ │ │ │ │ ├── b476f1461388bf4907634009904739e6_is.jpg │ │ │ │ │ ├── bc93fd5289c13c06fd569bc4f6df821e_is.jpg │ │ │ │ │ ├── c54bb4367803ba590035d2d89d1a84dd_is.jpg │ │ │ │ │ ├── cafae465b8ea283498c69ab9757f86ba_xl.jpg │ │ │ │ │ ├── e8757728eb70adeb8ebaa0864874c29d_is.jpg │ │ │ │ │ ├── ec6bb3fa05625b848ac4d475ecce35c9_is.jpg │ │ │ │ │ ├── f99201e42237de4fb7f8d5fbf9a2d270_is.jpg │ │ │ │ │ ├── fff25000064308791f739149af611439_is.jpg │ │ │ │ │ ├── 0b856ef58f76b7c83a0e130f6ef71281_200x112.jpg │ │ │ │ │ ├── 0cffb89d0b0bd4e726ae54b212a31c3b_200x112.jpg │ │ │ │ │ ├── 23cace5cf60f39dbc095bd7a12b2cfad_200x112.jpg │ │ │ │ │ ├── 31826765d442d8222a05cd67d0643a25_270x225.png │ │ │ │ │ ├── 450c9d9f5240f05f73d21fe3ae76f1a6_200x112.jpg │ │ │ │ │ ├── 558b3e3d8be209247159ba4f83ab1c02_200x112.jpg │ │ │ │ │ ├── 66a689b2c60557eae79d839aaedf48b0_200x112.png │ │ │ │ │ ├── 7412830858217e93f6c5d06f6328cbd5_200x112.png │ │ │ │ │ ├── 9cfe980ca44e38bd9b0e5c3dee5b0f3e_270x225.jpg │ │ │ │ │ ├── 9ec0168d1b210d9b8f089e16f521b82b_200x112.jpg │ │ │ │ │ ├── aa49b7ceff22ea68ca5f747115cd17af_200x112.jpg │ │ │ │ │ ├── c07f969d8dcc6251ef114fe6b1a9a563_200x112.jpg │ │ │ │ │ ├── c898060535edfdbe4147d2135c29787f_200x112.jpg │ │ │ │ │ ├── d6842d77b4bda238e0db09217e3d2f8d_270x225.jpg │ │ │ │ │ ├── f1148eb1c7170cabb0a78dad73b590f6_200x112.jpg │ │ │ │ │ ├── facfb45ac94f174655695853d4470bac_200x112.jpg │ │ │ │ │ ├── fb05f15bf8bffd1590df442ff6ba7812_200x112.jpg │ │ │ │ │ └── fb6c4dd60a9f19f5fcd8265395e11f9e_200x112.jpg │ │ │ └── scripts │ │ │ │ ├── main │ │ │ │ ├── site │ │ │ │ │ ├── follow.js │ │ │ │ │ ├── profile.js │ │ │ │ │ ├── home.js │ │ │ │ │ └── detail.js │ │ │ │ ├── base │ │ │ │ │ ├── util.js │ │ │ │ │ ├── event.js │ │ │ │ │ └── base.js │ │ │ │ ├── util │ │ │ │ │ ├── business.js │ │ │ │ │ └── action.js │ │ │ │ └── component │ │ │ │ │ ├── popupAdd.js │ │ │ │ │ ├── popupMsg.js │ │ │ │ │ ├── component.js │ │ │ │ │ └── popup.js │ │ │ │ ├── instant.e7a17de6.js │ │ │ │ └── page-index.d7b54ac7.js │ │ ├── mybatis │ │ │ ├── mybatis-config.xml │ │ │ └── dao │ │ │ │ └── QuestionDAO.xml │ │ ├── templates │ │ │ ├── js.html │ │ │ └── login.html │ │ ├── application.yml │ │ └── Spider.py │ └── java │ │ └── cn │ │ └── fciasth │ │ └── zhihu │ │ ├── bean │ │ ├── EntityType.java │ │ ├── HostHolder.java │ │ ├── LoginTicket.java │ │ ├── User.java │ │ ├── Comment.java │ │ ├── Message.java │ │ └── Question.java │ │ ├── async │ │ ├── EventHandler.java │ │ ├── EventType.java │ │ ├── EventProducer.java │ │ ├── handler │ │ │ ├── LikeHandler.java │ │ │ └── FollowHandler.java │ │ ├── EventModel.java │ │ └── EventConsumer.java │ │ ├── vo │ │ └── ViewObject.java │ │ ├── dao │ │ ├── QuestionRepository.java │ │ ├── LoginTicketDAO.java │ │ ├── UserDAO.java │ │ ├── QuestionDAO.java │ │ ├── CommentDAO.java │ │ └── MessageDAO.java │ │ ├── ZhihuApplication.java │ │ ├── config │ │ ├── MyWebMvcConfig.java │ │ └── DruidConfig.java │ │ ├── service │ │ ├── MessageService.java │ │ ├── QuestionService.java │ │ ├── SearchService.java │ │ ├── CommentService.java │ │ ├── LikeService.java │ │ ├── UserService.java │ │ ├── FollowService.java │ │ └── SensitiveService.java │ │ ├── util │ │ ├── RedisKeyUtil.java │ │ ├── CommonUtils.java │ │ └── JedisAdapter.java │ │ ├── controller │ │ ├── CommentController.java │ │ ├── SearchController.java │ │ ├── LikeController.java │ │ ├── LoginController.java │ │ ├── IndexController.java │ │ ├── QuestionController.java │ │ ├── MessageController.java │ │ └── FollowController.java │ │ └── interceptor │ │ └── PassportInterceptor.java └── test │ └── java │ └── cn │ └── fciasth │ └── zhihu │ └── ZhihuApplicationTests.java ├── .gitignore ├── README.md └── pom.xml /src/main/resources/SensitiveWords.txt: -------------------------------------------------------------------------------- 1 | 嫖娼 2 | 赌博 3 | 情赌 4 | 色情 5 | 酒鬼 -------------------------------------------------------------------------------- /src/main/resources/static/images/img/个人主页.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/img/个人主页.png -------------------------------------------------------------------------------- /src/main/resources/static/images/img/发表问题.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/img/发表问题.png -------------------------------------------------------------------------------- /src/main/resources/static/images/img/注册登录.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/img/注册登录.png -------------------------------------------------------------------------------- /src/main/resources/static/images/img/私信页面.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/img/私信页面.png -------------------------------------------------------------------------------- /src/main/resources/static/images/img/首页.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/img/首页.png -------------------------------------------------------------------------------- /src/main/resources/static/images/res/nk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/nk.png -------------------------------------------------------------------------------- /src/main/resources/static/images/img/查看问题,评论.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/img/查看问题,评论.png -------------------------------------------------------------------------------- /src/main/resources/static/images/res/captcha.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/captcha.gif -------------------------------------------------------------------------------- /src/main/resources/static/images/res/102799979_m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/102799979_m.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/1ce495b02_m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/1ce495b02_m.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/935f87219_m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/935f87219_m.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/a3f80f6a6_m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/a3f80f6a6_m.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/aadd7b895_m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/aadd7b895_m.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/aadd7b895_s.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/aadd7b895_s.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/b144d91ec_m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/b144d91ec_m.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/c28e9af7f_m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/c28e9af7f_m.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/c79247853_m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/c79247853_m.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/c94c92af8_m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/c94c92af8_m.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/da8e974dc_m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/da8e974dc_m.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/da8e974dc_s.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/da8e974dc_s.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/e6f4caaaa_m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/e6f4caaaa_m.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/070a9fb26_is.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/070a9fb26_is.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/6088e39f2_is.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/6088e39f2_is.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/b85ddd8aa_xl.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/b85ddd8aa_xl.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/da8e974dc_is.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/da8e974dc_is.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/e174d6d0c_is.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/e174d6d0c_is.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/hour.3d371c99.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/hour.3d371c99.png -------------------------------------------------------------------------------- /src/main/resources/static/images/res/nacl.656ec1c4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/nacl.656ec1c4.png -------------------------------------------------------------------------------- /src/main/resources/static/images/res/weekly.65279d61.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/weekly.65279d61.png -------------------------------------------------------------------------------- /src/main/resources/static/images/img/spinner2.8f60205d.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/img/spinner2.8f60205d.gif -------------------------------------------------------------------------------- /src/main/resources/static/images/img/sprites.auto.915a539c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/img/sprites.auto.915a539c.png -------------------------------------------------------------------------------- /src/main/resources/static/images/img/sprites-1.9.2.4c54885a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/img/sprites-1.9.2.4c54885a.png -------------------------------------------------------------------------------- /src/main/resources/static/images/img/sprites@2x.auto.dd5c79c1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/img/sprites@2x.auto.dd5c79c1.png -------------------------------------------------------------------------------- /src/main/resources/static/images/img/sprites-1.9.2@2x.6e638473.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/img/sprites-1.9.2@2x.6e638473.png -------------------------------------------------------------------------------- /src/main/resources/static/images/res/0ba3da3f03ced7a438118b0be77df56c_m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/0ba3da3f03ced7a438118b0be77df56c_m.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/0c6a39621ab1d456b1e6e492d0becc0c_s.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/0c6a39621ab1d456b1e6e492d0becc0c_s.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/0cf21546298ad1ed3cb64be61d822c27_m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/0cf21546298ad1ed3cb64be61d822c27_m.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/11be4a90ed938abfbab4899df56ee754_s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/11be4a90ed938abfbab4899df56ee754_s.png -------------------------------------------------------------------------------- /src/main/resources/static/images/res/12a8e1ce5ad8060fddb93ae2df98028b_m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/12a8e1ce5ad8060fddb93ae2df98028b_m.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/19456ebfe8b207320735f282769ac635_s.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/19456ebfe8b207320735f282769ac635_s.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/1ac7840eeb19ada0bbf85f51702d5784_s.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/1ac7840eeb19ada0bbf85f51702d5784_s.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/24ce38dd5fc2a0c4e0525e577eef7d64_s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/24ce38dd5fc2a0c4e0525e577eef7d64_s.png -------------------------------------------------------------------------------- /src/main/resources/static/images/res/2e21e58a990f5c756e813a64a4bba14c_m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/2e21e58a990f5c756e813a64a4bba14c_m.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/33fb6f51a3f4e16b6e89172040451dca_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/33fb6f51a3f4e16b6e89172040451dca_b.png -------------------------------------------------------------------------------- /src/main/resources/static/images/res/3b673d6335ef6788d1659ee2b6381e97_m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/3b673d6335ef6788d1659ee2b6381e97_m.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/41d652d947a489e056b0179ba137294b_m.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/41d652d947a489e056b0179ba137294b_m.png -------------------------------------------------------------------------------- /src/main/resources/static/images/res/4d104b6c6a08f7e1a48f4f32c88b1ce2_m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/4d104b6c6a08f7e1a48f4f32c88b1ce2_m.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/51559bbebaa7fd395c271b7b1c8b9f26_m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/51559bbebaa7fd395c271b7b1c8b9f26_m.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/6ceea810748d179f57cac0baa5cf9592_s.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/6ceea810748d179f57cac0baa5cf9592_s.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/6cfbfc0c5e3c5ecd8784f7e733a75b4f_m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/6cfbfc0c5e3c5ecd8784f7e733a75b4f_m.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/845303838eca4a5b5f03cc3ca994ec28_s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/845303838eca4a5b5f03cc3ca994ec28_s.png -------------------------------------------------------------------------------- /src/main/resources/static/images/res/845c492813e72b85c6e11cccf8ed0ff8_m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/845c492813e72b85c6e11cccf8ed0ff8_m.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/a57fdaff90865eaaef2e87051624862b_m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/a57fdaff90865eaaef2e87051624862b_m.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/actioncard-validation@2x.65147043.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/actioncard-validation@2x.65147043.png -------------------------------------------------------------------------------- /src/main/resources/static/images/res/b3aadf89405941b05a5ce00fb06f8281_m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/b3aadf89405941b05a5ce00fb06f8281_m.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/b4a6228e6810d38e19e491c173af4d5c_m.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/b4a6228e6810d38e19e491c173af4d5c_m.png -------------------------------------------------------------------------------- /src/main/resources/static/images/res/badaaf312e59c125928bd1ea2d4b5a51_b.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/badaaf312e59c125928bd1ea2d4b5a51_b.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/bb73b7fb48b0cb63aa573415dfe4d0c5_m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/bb73b7fb48b0cb63aa573415dfe4d0c5_m.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/be39d110759e68f389b7d2934d7353bc_m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/be39d110759e68f389b7d2934d7353bc_m.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/cbc5d3c6f333215a1c480cb3b4735b45_m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/cbc5d3c6f333215a1c480cb3b4735b45_m.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/d207854fffc9e0289fbd6bbbb3986988_s.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/d207854fffc9e0289fbd6bbbb3986988_s.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/d822a919d93a761634a67c2022a3f614_m.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/d822a919d93a761634a67c2022a3f614_m.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/df4aa616fdcfbd861c010ff71aaef95c_b.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/df4aa616fdcfbd861c010ff71aaef95c_b.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/fa70eff301ba417d4a9d3f55d603a29e_s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/fa70eff301ba417d4a9d3f55d603a29e_s.png -------------------------------------------------------------------------------- /src/main/resources/static/images/res/10a4cd7fb082375332be33eadfd14c58_is.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/10a4cd7fb082375332be33eadfd14c58_is.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/11ba31c8bb5473a44b7690eff24b9123_is.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/11ba31c8bb5473a44b7690eff24b9123_is.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/272627e471a533f58f319a9e600e0a94_is.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/272627e471a533f58f319a9e600e0a94_is.png -------------------------------------------------------------------------------- /src/main/resources/static/images/res/4528283ed249589634546327431667bf_is.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/4528283ed249589634546327431667bf_is.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/6c76223a5aef2f1d29c680a2524de791_is.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/6c76223a5aef2f1d29c680a2524de791_is.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/6fd46860a4b6cbc1e52d676f217ea9fd_is.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/6fd46860a4b6cbc1e52d676f217ea9fd_is.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/7986ef6045f2cef8352be5affce5f7d1_is.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/7986ef6045f2cef8352be5affce5f7d1_is.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/837b60aa36029a2309974e74de3b62e9_xl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/837b60aa36029a2309974e74de3b62e9_xl.png -------------------------------------------------------------------------------- /src/main/resources/static/images/res/975baaf73fd76f48ce6f05e19b176878_xl.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/975baaf73fd76f48ce6f05e19b176878_xl.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/a4df63114bd9374a73775e30db1cdd36_is.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/a4df63114bd9374a73775e30db1cdd36_is.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/actioncard-suggested-avatar.c5af416d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/actioncard-suggested-avatar.c5af416d.png -------------------------------------------------------------------------------- /src/main/resources/static/images/res/b476f1461388bf4907634009904739e6_is.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/b476f1461388bf4907634009904739e6_is.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/bc93fd5289c13c06fd569bc4f6df821e_is.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/bc93fd5289c13c06fd569bc4f6df821e_is.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/c54bb4367803ba590035d2d89d1a84dd_is.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/c54bb4367803ba590035d2d89d1a84dd_is.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/cafae465b8ea283498c69ab9757f86ba_xl.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/cafae465b8ea283498c69ab9757f86ba_xl.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/e8757728eb70adeb8ebaa0864874c29d_is.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/e8757728eb70adeb8ebaa0864874c29d_is.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/ec6bb3fa05625b848ac4d475ecce35c9_is.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/ec6bb3fa05625b848ac4d475ecce35c9_is.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/f99201e42237de4fb7f8d5fbf9a2d270_is.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/f99201e42237de4fb7f8d5fbf9a2d270_is.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/fff25000064308791f739149af611439_is.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/fff25000064308791f739149af611439_is.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/0b856ef58f76b7c83a0e130f6ef71281_200x112.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/0b856ef58f76b7c83a0e130f6ef71281_200x112.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/0cffb89d0b0bd4e726ae54b212a31c3b_200x112.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/0cffb89d0b0bd4e726ae54b212a31c3b_200x112.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/23cace5cf60f39dbc095bd7a12b2cfad_200x112.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/23cace5cf60f39dbc095bd7a12b2cfad_200x112.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/31826765d442d8222a05cd67d0643a25_270x225.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/31826765d442d8222a05cd67d0643a25_270x225.png -------------------------------------------------------------------------------- /src/main/resources/static/images/res/450c9d9f5240f05f73d21fe3ae76f1a6_200x112.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/450c9d9f5240f05f73d21fe3ae76f1a6_200x112.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/558b3e3d8be209247159ba4f83ab1c02_200x112.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/558b3e3d8be209247159ba4f83ab1c02_200x112.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/66a689b2c60557eae79d839aaedf48b0_200x112.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/66a689b2c60557eae79d839aaedf48b0_200x112.png -------------------------------------------------------------------------------- /src/main/resources/static/images/res/7412830858217e93f6c5d06f6328cbd5_200x112.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/7412830858217e93f6c5d06f6328cbd5_200x112.png -------------------------------------------------------------------------------- /src/main/resources/static/images/res/9cfe980ca44e38bd9b0e5c3dee5b0f3e_270x225.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/9cfe980ca44e38bd9b0e5c3dee5b0f3e_270x225.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/9ec0168d1b210d9b8f089e16f521b82b_200x112.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/9ec0168d1b210d9b8f089e16f521b82b_200x112.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/aa49b7ceff22ea68ca5f747115cd17af_200x112.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/aa49b7ceff22ea68ca5f747115cd17af_200x112.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/c07f969d8dcc6251ef114fe6b1a9a563_200x112.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/c07f969d8dcc6251ef114fe6b1a9a563_200x112.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/c898060535edfdbe4147d2135c29787f_200x112.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/c898060535edfdbe4147d2135c29787f_200x112.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/d6842d77b4bda238e0db09217e3d2f8d_270x225.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/d6842d77b4bda238e0db09217e3d2f8d_270x225.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/f1148eb1c7170cabb0a78dad73b590f6_200x112.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/f1148eb1c7170cabb0a78dad73b590f6_200x112.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/facfb45ac94f174655695853d4470bac_200x112.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/facfb45ac94f174655695853d4470bac_200x112.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/fb05f15bf8bffd1590df442ff6ba7812_200x112.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/fb05f15bf8bffd1590df442ff6ba7812_200x112.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/res/fb6c4dd60a9f19f5fcd8265395e11f9e_200x112.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcxiaotudou/zhihu/HEAD/src/main/resources/static/images/res/fb6c4dd60a9f19f5fcd8265395e11f9e_200x112.jpg -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/bean/EntityType.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.bean; 2 | 3 | 4 | public class EntityType { 5 | public static int ENTITY_QUESTION = 1; 6 | public static int ENTITY_COMMENT = 2; 7 | public static int ENTITY_USER = 3; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/async/EventHandler.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.async; 2 | 3 | import java.util.List; 4 | 5 | public interface EventHandler { 6 | 7 | void doHandle(EventModel model); 8 | 9 | List getSupportEventTypes(); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/resources/static/scripts/main/site/follow.js: -------------------------------------------------------------------------------- 1 | (function (window, undefined) { 2 | var Business = Base.getClass('main.util.Business'); 3 | 4 | Base.ready({ 5 | initialize: fInitialize 6 | }); 7 | 8 | function fInitialize() { 9 | Business.followUser(); 10 | } 11 | })(); -------------------------------------------------------------------------------- /src/main/resources/static/scripts/main/site/profile.js: -------------------------------------------------------------------------------- 1 | (function (window, undefined) { 2 | var Business = Base.getClass('main.util.Business'); 3 | 4 | Base.ready({ 5 | initialize: fInitialize 6 | }); 7 | 8 | function fInitialize() { 9 | Business.followUser(); 10 | } 11 | })(); -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/async/EventType.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.async; 2 | 3 | public enum EventType { 4 | LIKE(0), 5 | COMMENT(1), 6 | LOGIN(2), 7 | MAIL(3), 8 | FOLLOW(4), 9 | UNFOLLOW(5); 10 | 11 | private int value; 12 | EventType(int value) { this.value = value; } 13 | public int getValue() { return value; } 14 | } 15 | -------------------------------------------------------------------------------- /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); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /build/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ 26 | /.mvn/ 27 | /mvnw.cmd 28 | /mvnw 29 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/vo/ViewObject.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.vo; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class ViewObject { 7 | 8 | private Map vo = new HashMap(); 9 | 10 | public void set(String key,Object value){ 11 | vo.put(key,value); 12 | } 13 | 14 | public Object get(String key){ 15 | return vo.get(key); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/bean/HostHolder.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.bean; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | @Component 6 | public class HostHolder { 7 | 8 | private static ThreadLocal users = new ThreadLocal(); 9 | 10 | public void setUser(User user){ 11 | users.set(user); 12 | } 13 | 14 | public User getUser(){ 15 | return users.get(); 16 | } 17 | 18 | public void clear(){ 19 | users.remove(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/dao/QuestionRepository.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.dao; 2 | 3 | import cn.fciasth.zhihu.bean.Question; 4 | import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.List; 8 | 9 | @Repository 10 | public interface QuestionRepository extends ElasticsearchRepository{ 11 | 12 | public List findQuestionsByContentLikeOrTitleLike(String title,String content); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/ZhihuApplication.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; 6 | 7 | 8 | @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) 9 | public class ZhihuApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(ZhihuApplication.class, args); 13 | } 14 | } 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/resources/mybatis/mybatis-config.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/config/MyWebMvcConfig.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.config; 2 | 3 | import cn.fciasth.zhihu.interceptor.PassportInterceptor; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 7 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 8 | 9 | @Configuration 10 | public class MyWebMvcConfig implements WebMvcConfigurer { 11 | 12 | @Autowired 13 | private PassportInterceptor passportInterceptor; 14 | 15 | @Override 16 | public void addInterceptors(InterceptorRegistry registry) { 17 | registry.addInterceptor(passportInterceptor); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/resources/mybatis/dao/QuestionDAO.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | question 8 | id, title, content, comment_count, created_date, user_id 9 | 10 | 22 | 23 | -------------------------------------------------------------------------------- /src/main/resources/templates/js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/dao/LoginTicketDAO.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.dao; 2 | 3 | import cn.fciasth.zhihu.bean.LoginTicket; 4 | import org.apache.ibatis.annotations.*; 5 | 6 | @Mapper 7 | public interface LoginTicketDAO { 8 | 9 | String TABLE_NAME = "login_ticket"; 10 | String INSERT_FIELDS = " user_id, expired, status, ticket "; 11 | String SELECT_FIELDS = " id, " + INSERT_FIELDS; 12 | 13 | @Insert({"insert into ", TABLE_NAME, "(", INSERT_FIELDS, 14 | ") values (#{userId},#{expired},#{status},#{ticket})"}) 15 | int addTicket(LoginTicket ticket); 16 | 17 | @Select({"select ", SELECT_FIELDS, " from ", TABLE_NAME, " where ticket=#{ticket}"}) 18 | LoginTicket selectByTicket(String ticket); 19 | 20 | @Update({"update ", TABLE_NAME, " set status=#{status} where ticket=#{ticket}"}) 21 | void updateStatus(@Param("ticket") String ticket, @Param("status") int status); 22 | } 23 | -------------------------------------------------------------------------------- /src/main/resources/static/scripts/main/site/home.js: -------------------------------------------------------------------------------- 1 | (function (window, undefined) { 2 | var PopupAdd = Base.getClass('main.component.PopupAdd'); 3 | var PopupMsg = Base.getClass('main.component.PopupMsg'); 4 | 5 | Base.ready({ 6 | initialize: fInitialize, 7 | binds: { 8 | 'click #zu-top-add-question': fClickAdd, 9 | 'click #zh-top-nav-count-wrap': fClickMsg 10 | } 11 | }); 12 | 13 | function fInitialize() { 14 | var that = this; 15 | } 16 | 17 | function fClickAdd() { 18 | var that = this; 19 | PopupAdd.show({ 20 | ok: function () { 21 | window.location.replace("/"); 22 | } 23 | }); 24 | } 25 | 26 | function fClickMsg() { 27 | var that = this; 28 | PopupMsg.show({ 29 | ok: function () { 30 | window.location.replace("/msg/list"); 31 | } 32 | }); 33 | } 34 | 35 | })(window); -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/dao/UserDAO.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.dao; 2 | 3 | import cn.fciasth.zhihu.bean.User; 4 | import org.apache.ibatis.annotations.*; 5 | 6 | @Mapper 7 | public interface UserDAO { 8 | 9 | String TABLE_NAME = " user "; 10 | String INSERT_FIELDS = " name,password,salt,head_url "; 11 | String SELECT_FIELDS = " id, "+INSERT_FIELDS; 12 | 13 | @Insert({"insert into ",TABLE_NAME ," (", INSERT_FIELDS, ") " + 14 | "values(#{name},#{password},#{salt},#{headUrl})"}) 15 | int addUser(User user); 16 | 17 | @Select({"select ",SELECT_FIELDS," from ",TABLE_NAME," where id = #{id}"}) 18 | User selectById(int id); 19 | 20 | @Select({"select ",SELECT_FIELDS," from ",TABLE_NAME," where name = #{name}"}) 21 | User selectByName(String name); 22 | 23 | @Update({"update ",TABLE_NAME," set password = #{password} where id = #{id}"}) 24 | void updatePassword(User user); 25 | 26 | @Delete({"delete from ",TABLE_NAME," where id = #{id}"}) 27 | void deleteById(int id); 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/async/EventProducer.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.async; 2 | 3 | import cn.fciasth.zhihu.controller.IndexController; 4 | import cn.fciasth.zhihu.util.JedisAdapter; 5 | import cn.fciasth.zhihu.util.RedisKeyUtil; 6 | import com.alibaba.fastjson.JSONObject; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.stereotype.Service; 11 | 12 | @Service 13 | public class EventProducer { 14 | 15 | private static final Logger logger = LoggerFactory.getLogger(EventProducer.class); 16 | 17 | @Autowired 18 | private JedisAdapter jedisAdapter; 19 | 20 | public Boolean fireEvent(EventModel eventModel){ 21 | try { 22 | String json = JSONObject.toJSONString(eventModel); 23 | String key = RedisKeyUtil.getEventQueueKey(); 24 | jedisAdapter.lpush(key,json); 25 | return true; 26 | }catch (Exception e){ 27 | logger.error("分发事件失败"+e.getMessage()); 28 | return false; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/bean/LoginTicket.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.bean; 2 | 3 | import java.util.Date; 4 | 5 | public class LoginTicket { 6 | private int id; 7 | private int userId; 8 | private Date expired; 9 | private int status;// 0有效,1无效 10 | private String ticket; 11 | 12 | public String getTicket() { 13 | return ticket; 14 | } 15 | 16 | public void setTicket(String ticket) { 17 | this.ticket = ticket; 18 | } 19 | 20 | public int getId() { 21 | return id; 22 | } 23 | 24 | public void setId(int id) { 25 | this.id = id; 26 | } 27 | 28 | public int getUserId() { 29 | return userId; 30 | } 31 | 32 | public void setUserId(int userId) { 33 | this.userId = userId; 34 | } 35 | 36 | public Date getExpired() { 37 | return expired; 38 | } 39 | 40 | public void setExpired(Date expired) { 41 | this.expired = expired; 42 | } 43 | 44 | public int getStatus() { 45 | return status; 46 | } 47 | 48 | public void setStatus(int status) { 49 | this.status = status; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/dao/QuestionDAO.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.dao; 2 | 3 | import cn.fciasth.zhihu.bean.Question; 4 | import cn.fciasth.zhihu.bean.User; 5 | import org.apache.ibatis.annotations.*; 6 | 7 | import java.util.List; 8 | 9 | @Mapper 10 | public interface QuestionDAO { 11 | 12 | String TABLE_NAME = " question "; 13 | String INSERT_FIELDS = " title,content,created_date,user_id,comment_count "; 14 | String SELECT_FIELDS = " id, "+INSERT_FIELDS; 15 | 16 | @Insert({"insert into ",TABLE_NAME ," (", INSERT_FIELDS, ") " + 17 | "values(#{title},#{content},#{createdDate},#{userId},#{commentCount})"}) 18 | int addQuestion(Question question); 19 | 20 | List selectLatestQuestions(@Param("userId") int userId, 21 | @Param("offset") int offset, 22 | @Param("limit") int limit); 23 | 24 | @Select({"select ",SELECT_FIELDS, " from ",TABLE_NAME," where id = #{id}"}) 25 | Question selectById(@Param("id") int id); 26 | 27 | @Update({"update ", TABLE_NAME, " set comment_count = #{commentCount} where id=#{id}"}) 28 | int updateCommentCount(@Param("id") int id, @Param("commentCount") int commentCount); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | # 数据源基本配置 4 | username: root 5 | password: 8469226 6 | driver-class-name: com.mysql.jdbc.Driver 7 | url: jdbc:mysql://localhost:3306/zhihu?useSSL=true 8 | type: com.alibaba.druid.pool.DruidDataSource 9 | # 数据源其他配置 10 | initialSize: 5 11 | minIdle: 5 12 | maxActive: 20 13 | maxWait: 60000 14 | timeBetweenEvictionRunsMillis: 60000 15 | minEvictableIdleTimeMillis: 300000 16 | validationQuery: SELECT 1 FROM DUAL 17 | testWhileIdle: true 18 | testOnBorrow: false 19 | testOnReturn: false 20 | poolPreparedStatements: true 21 | # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙 22 | filters: stat,wall 23 | maxPoolPreparedStatementPerConnectionSize: 20 24 | useGlobalDataSourceStat: true 25 | connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500 26 | jpa: 27 | show-sql: true 28 | data: 29 | elasticsearch: 30 | cluster-name: elasticsearch 31 | cluster-nodes: localhost:9300 32 | mybatis: 33 | config-location: classpath:mybatis/mybatis-config.xml 34 | mapper-locations: classpath:mybatis/dao/*.xml 35 | # schema: 36 | # - classpath:sql/department.sql 37 | # - classpath:sql/employee.sql 38 | # initialization-mode: always 39 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/bean/User.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.bean; 2 | 3 | public class User { 4 | private int id; 5 | private String name; 6 | private String password; 7 | private String salt; 8 | private String headUrl; 9 | 10 | public User() { 11 | 12 | } 13 | public User(String name) { 14 | this.name = name; 15 | this.password = ""; 16 | this.salt = ""; 17 | this.headUrl = ""; 18 | } 19 | 20 | public String getName() { 21 | return name; 22 | } 23 | 24 | public void setName(String name) { 25 | this.name = name; 26 | } 27 | 28 | public String getPassword() { 29 | return password; 30 | } 31 | 32 | public void setPassword(String password) { 33 | this.password = password; 34 | } 35 | 36 | public String getSalt() { 37 | return salt; 38 | } 39 | 40 | public void setSalt(String salt) { 41 | this.salt = salt; 42 | } 43 | 44 | public String getHeadUrl() { 45 | return headUrl; 46 | } 47 | 48 | public void setHeadUrl(String headUrl) { 49 | this.headUrl = headUrl; 50 | } 51 | 52 | public int getId() { 53 | return id; 54 | } 55 | 56 | public void setId(int id) { 57 | this.id = id; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/service/MessageService.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.service; 2 | 3 | import cn.fciasth.zhihu.bean.Message; 4 | import cn.fciasth.zhihu.dao.MessageDAO; 5 | import org.apache.ibatis.annotations.Param; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.web.util.HtmlUtils; 9 | 10 | import java.util.List; 11 | 12 | @Service 13 | public class MessageService { 14 | 15 | @Autowired 16 | MessageDAO messageDAO; 17 | 18 | @Autowired 19 | SensitiveService sensitiveService; 20 | 21 | public int addMessage(Message message) { 22 | message.setContent(sensitiveService.filter(message.getContent())); 23 | return messageDAO.addMessage(message); 24 | } 25 | 26 | public List getConversationDetail(String conversationId, int offset, int limit) { 27 | return messageDAO.getConversationDetail(conversationId, offset, limit); 28 | } 29 | 30 | public List getConversationList(int userId, int offset, int limit) { 31 | return messageDAO.getConversationList(userId, offset, limit); 32 | } 33 | 34 | public int getConvesationUnreadCount(int userId, String conversationId) { 35 | return messageDAO.getConvesationUnreadCount(userId, conversationId); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/service/QuestionService.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.service; 2 | 3 | import cn.fciasth.zhihu.bean.Question; 4 | import cn.fciasth.zhihu.dao.QuestionDAO; 5 | import cn.fciasth.zhihu.util.CommonUtils; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.web.util.HtmlUtils; 9 | 10 | import java.util.List; 11 | 12 | @Service 13 | public class QuestionService { 14 | 15 | @Autowired 16 | private QuestionDAO questionDAO; 17 | 18 | @Autowired 19 | private SensitiveService sensitiveService; 20 | 21 | public List getLatestQuestions(int userId,int offset,int limit){ 22 | return questionDAO.selectLatestQuestions(userId,offset,limit); 23 | } 24 | 25 | public int addQuestion(Question question){ 26 | question.setTitle(HtmlUtils.htmlEscape(question.getTitle())); 27 | question.setContent(HtmlUtils.htmlEscape(question.getContent())); 28 | //敏感词过滤 29 | question.setTitle(sensitiveService.filter(question.getTitle())); 30 | question.setContent(sensitiveService.filter(question.getContent())); 31 | return questionDAO.addQuestion(question)>0 ? question.getId():0; 32 | } 33 | 34 | public Question selectById(int id){ 35 | return questionDAO.selectById(id); 36 | } 37 | 38 | public int updateCommentCount(int id, int count) { 39 | return questionDAO.updateCommentCount(id, count); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zhihu 2 | 一个仿知乎的小demo,前端:thymeleaf模板,后端:springboot+mybatis+redis+elasticsearch 3 | 4 | 5 | 6 | ### 功能 7 | 8 | 1. 登录/注册 9 | 2. 发表/评论问题 10 | 3. 用户相互关注 11 | 4. 关注问题 12 | 5. 问题或评论进行点赞点踩 13 | 6. 用户站内私信和通知 14 | 7. 全文检索 15 | 8. 问题敏感词过滤 16 | 17 | ### 效果 18 | 现有数据使用Pyspider爬取自[https://www.v2ex.com/](https://www.v2ex.com/) Pyspider代码见:[Pyspider代码](https://github.com/tcxiaotudou/zhihu/blob/master/src/main/resources/Spider.py) 19 | 20 | 详情可见: [http://193.112.108.187:8080](http://193.112.108.187:8080) ,由于es在Windows和linux的版本问题,演示网站的全文检索功能暂不可用。 21 | 22 | 首页: 23 | 24 | ![img](https://github.com/tcxiaotudou/zhihu/blob/master/src/main/resources/static/images/img/%E9%A6%96%E9%A1%B5.png) 25 | 26 | 27 | 28 | 注册登录: 29 | 30 | ![这里写图片描述](https://github.com/tcxiaotudou/zhihu/blob/master/src/main/resources/static/images/img/%E6%B3%A8%E5%86%8C%E7%99%BB%E5%BD%95.png) 31 | 32 | 33 | 发表问题: 34 | 35 | ![这里写图片描述](https://github.com/tcxiaotudou/zhihu/blob/master/src/main/resources/static/images/img/%E5%8F%91%E8%A1%A8%E9%97%AE%E9%A2%98.png) 36 | 37 | 38 | 查看问题/评论: 39 | 40 | ![这里写图片描述](https://github.com/tcxiaotudou/zhihu/blob/master/src/main/resources/static/images/img/%E6%9F%A5%E7%9C%8B%E9%97%AE%E9%A2%98%2C%E8%AF%84%E8%AE%BA.png) 41 | 私信页面: 42 | 43 | ![这里写图片描述](https://github.com/tcxiaotudou/zhihu/blob/master/src/main/resources/static/images/img/%E7%A7%81%E4%BF%A1%E9%A1%B5%E9%9D%A2.png) 44 | 个人主页: 45 | 46 | ![这里写图片描述](https://github.com/tcxiaotudou/zhihu/blob/master/src/main/resources/static/images/img/%E4%B8%AA%E4%BA%BA%E4%B8%BB%E9%A1%B5.png) 47 | 48 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/service/SearchService.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.service; 2 | 3 | import cn.fciasth.zhihu.bean.Question; 4 | import cn.fciasth.zhihu.dao.QuestionRepository; 5 | import cn.fciasth.zhihu.vo.ViewObject; 6 | import org.elasticsearch.index.query.QueryStringQueryBuilder; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.data.domain.Page; 9 | import org.springframework.data.domain.PageRequest; 10 | import org.springframework.data.domain.Pageable; 11 | import org.springframework.data.domain.Sort; 12 | import org.springframework.stereotype.Service; 13 | 14 | import java.util.ArrayList; 15 | import java.util.Iterator; 16 | import java.util.List; 17 | 18 | @Service 19 | public class SearchService { 20 | 21 | @Autowired 22 | private QuestionRepository questionRepository; 23 | 24 | public List searchQuestion(String keywords){ 25 | return questionRepository.findQuestionsByContentLikeOrTitleLike(keywords,keywords); 26 | } 27 | 28 | public List testSearch(String keywords,int offset,int limit){ 29 | QueryStringQueryBuilder builder = new QueryStringQueryBuilder(keywords); 30 | Pageable pageable = PageRequest.of(offset,limit,Sort.by(Sort.Direction.DESC, "createdDate")); 31 | Page page = questionRepository.search(builder,pageable); 32 | System.out.println("总条数:"+page.getTotalElements()); 33 | System.out.println("总页数:"+page.getTotalPages()); 34 | return page.getContent(); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/service/CommentService.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.service; 2 | 3 | import cn.fciasth.zhihu.bean.Comment; 4 | import cn.fciasth.zhihu.bean.Question; 5 | import cn.fciasth.zhihu.dao.CommentDAO; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.web.util.HtmlUtils; 9 | 10 | import java.util.List; 11 | 12 | @Service 13 | public class CommentService { 14 | 15 | @Autowired 16 | private CommentDAO commentDAO; 17 | 18 | @Autowired 19 | private SensitiveService sensitiveService; 20 | 21 | public int addComment(Comment comment){ 22 | comment.setContent(HtmlUtils.htmlEscape(comment.getContent())); 23 | comment.setContent(sensitiveService.filter(comment.getContent())); 24 | return commentDAO.addComment(comment) > 0 ? comment.getId():0; 25 | } 26 | 27 | public int getCommentCount(int entityId,int entityType){ 28 | return commentDAO.getCommentCount(entityId,entityType); 29 | } 30 | 31 | public List selectCommentsByEntity(int entityId,int entityType){ 32 | return commentDAO.selectCommentByEntity(entityId,entityType); 33 | } 34 | 35 | public boolean deleteComment(int commentId){ 36 | return commentDAO.updateStatus(commentId,1) > 0; 37 | } 38 | 39 | public Comment getCommentById(int id) { 40 | return commentDAO.getCommentById(id); 41 | } 42 | 43 | public int getUserCommentCount(int userId) { 44 | return commentDAO.getUserCommentCount(userId); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/bean/Comment.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.bean; 2 | 3 | import java.util.Date; 4 | 5 | public class Comment { 6 | private int id; 7 | private int userId; 8 | private int entityId; 9 | private int entityType; 10 | private String content; 11 | private Date createdDate; 12 | private int status; 13 | 14 | public int getId() { 15 | return id; 16 | } 17 | 18 | public void setId(int id) { 19 | this.id = id; 20 | } 21 | 22 | public int getUserId() { 23 | return userId; 24 | } 25 | 26 | public void setUserId(int userId) { 27 | this.userId = userId; 28 | } 29 | 30 | public int getEntityId() { 31 | return entityId; 32 | } 33 | 34 | public void setEntityId(int entityId) { 35 | this.entityId = entityId; 36 | } 37 | 38 | public int getEntityType() { 39 | return entityType; 40 | } 41 | 42 | public void setEntityType(int entityType) { 43 | this.entityType = entityType; 44 | } 45 | 46 | public String getContent() { 47 | return content; 48 | } 49 | 50 | public void setContent(String content) { 51 | this.content = content; 52 | } 53 | 54 | public Date getCreatedDate() { 55 | return createdDate; 56 | } 57 | 58 | public void setCreatedDate(Date createdDate) { 59 | this.createdDate = createdDate; 60 | } 61 | 62 | public int getStatus() { 63 | return status; 64 | } 65 | 66 | public void setStatus(int status) { 67 | this.status = status; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/util/RedisKeyUtil.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.util; 2 | 3 | import sun.dc.pr.PRError; 4 | 5 | public class RedisKeyUtil { 6 | 7 | private static String SPLIT = ":"; 8 | private static String BIZ_LIKE = "LIKE"; 9 | private static String BIZ_DISLIKE = "DISLIKE"; 10 | private static String BIZ_EVENTQUEUE = "EVENT_QUEUE"; 11 | // 获取粉丝 12 | private static String BIZ_FOLLOWER = "FOLLOWER"; 13 | // 关注对象 14 | private static String BIZ_FOLLOWEE = "FOLLOWEE"; 15 | private static String BIZ_TIMELINE = "TIMELINE"; 16 | 17 | 18 | public static String getLikeKey(int entityType, int entityId){ 19 | return BIZ_LIKE + SPLIT + String.valueOf(entityType) + SPLIT + String.valueOf(entityId); 20 | } 21 | public static String getDisLikeKey(int entityType, int entityId){ 22 | return BIZ_DISLIKE + SPLIT + String.valueOf(entityType) + SPLIT + String.valueOf(entityId); 23 | } 24 | public static String getEventQueueKey(){ 25 | return BIZ_EVENTQUEUE; 26 | } 27 | // 某个实体的粉丝key 28 | public static String getFollowerKey(int entityType, int entityId) { 29 | return BIZ_FOLLOWER + SPLIT + String.valueOf(entityType) + SPLIT + String.valueOf(entityId); 30 | } 31 | 32 | // 每个用户对某类实体的关注key 33 | public static String getFolloweeKey(int userId, int entityType) { 34 | return BIZ_FOLLOWEE + SPLIT + String.valueOf(userId) + SPLIT + String.valueOf(entityType); 35 | } 36 | 37 | public static String getTimelineKey(int userId) { 38 | return BIZ_TIMELINE + SPLIT + String.valueOf(userId); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/async/handler/LikeHandler.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.async.handler; 2 | 3 | 4 | import cn.fciasth.zhihu.async.EventHandler; 5 | import cn.fciasth.zhihu.async.EventModel; 6 | import cn.fciasth.zhihu.async.EventType; 7 | import cn.fciasth.zhihu.bean.Message; 8 | import cn.fciasth.zhihu.bean.User; 9 | import cn.fciasth.zhihu.service.MessageService; 10 | import cn.fciasth.zhihu.service.UserService; 11 | import cn.fciasth.zhihu.util.CommonUtils; 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 nowcoder on 2016/7/30. 21 | */ 22 | @Component 23 | public class LikeHandler implements EventHandler { 24 | @Autowired 25 | MessageService messageService; 26 | 27 | @Autowired 28 | UserService userService; 29 | 30 | @Override 31 | public void doHandle(EventModel model) { 32 | Message message = new Message(); 33 | message.setFromId(CommonUtils.SYSTEM_USERID); 34 | message.setToId(model.getEntityOwnerId()); 35 | message.setCreatedDate(new Date()); 36 | User user = userService.getUser(model.getActorId()); 37 | message.setContent("用户" + user.getName() 38 | + "赞了你的评论,http://127.0.0.1:8080/question/" + model.getExt("questionId")); 39 | messageService.addMessage(message); 40 | } 41 | 42 | @Override 43 | public List getSupportEventTypes() { 44 | return Arrays.asList(EventType.LIKE); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/bean/Message.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.bean; 2 | 3 | import java.util.Date; 4 | 5 | public class Message { 6 | private int id; 7 | private int fromId; 8 | private int toId; 9 | private String content; 10 | private Date createdDate; 11 | private int hasRead; 12 | private String conversationId; 13 | 14 | public int getId() { 15 | return id; 16 | } 17 | 18 | public void setId(int id) { 19 | this.id = id; 20 | } 21 | 22 | public int getFromId() { 23 | return fromId; 24 | } 25 | 26 | public void setFromId(int fromId) { 27 | this.fromId = fromId; 28 | } 29 | 30 | public int getToId() { 31 | return toId; 32 | } 33 | 34 | public void setToId(int toId) { 35 | this.toId = toId; 36 | } 37 | 38 | public String getContent() { 39 | return content; 40 | } 41 | 42 | public void setContent(String content) { 43 | this.content = content; 44 | } 45 | 46 | public Date getCreatedDate() { 47 | return createdDate; 48 | } 49 | 50 | public void setCreatedDate(Date createdDate) { 51 | this.createdDate = createdDate; 52 | } 53 | 54 | public int getHasRead() { 55 | return hasRead; 56 | } 57 | 58 | public void setHasRead(int hasRead) { 59 | this.hasRead = hasRead; 60 | } 61 | 62 | public String getConversationId() { 63 | if (fromId < toId) { 64 | return String.format("%d_%d", fromId, toId); 65 | } 66 | return String.format("%d_%d", toId, fromId); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/dao/CommentDAO.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.dao; 2 | 3 | import cn.fciasth.zhihu.bean.Comment; 4 | import cn.fciasth.zhihu.bean.Question; 5 | import org.apache.ibatis.annotations.*; 6 | 7 | import java.util.List; 8 | 9 | @Mapper 10 | public interface CommentDAO { 11 | 12 | String TABLE_NAME = " comment "; 13 | String INSERT_FIELDS = " user_id, content, created_date, entity_id, entity_type, status "; 14 | String SELECT_FIELDS = " id, "+INSERT_FIELDS; 15 | 16 | @Select({"select ", SELECT_FIELDS, " from ", TABLE_NAME, " where id=#{id}"}) 17 | Comment getCommentById(int id); 18 | 19 | @Insert({"insert into ",TABLE_NAME ," (", INSERT_FIELDS, ") " + 20 | "values(#{userId},#{content},#{createdDate},#{entityId},#{entityType},#{status})"}) 21 | int addComment(Comment comment); 22 | 23 | @Select({"select ",SELECT_FIELDS," from ",TABLE_NAME,"" + 24 | " where entity_id = #{entityId} and entity_type = #{entityType} order by created_date desc"}) 25 | List selectCommentByEntity(@Param("entityId") int entityId, @Param("entityType") int entityType); 26 | 27 | @Select({"select count(id) from ",TABLE_NAME," where entity_id = #{entityId} and entity_type = #{entityType}"}) 28 | int getCommentCount(@Param("entityId") int entityId, @Param("entityType") int entityType); 29 | 30 | @Update({"update ",TABLE_NAME, " set status = #{status} where id = #{id}"}) 31 | int updateStatus(@Param("id") int id,@Param("status") int status); 32 | 33 | @Select({"select count(id) from ", TABLE_NAME, " where user_id=#{userId}"}) 34 | int getUserCommentCount(int userId); 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/dao/MessageDAO.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.dao; 2 | 3 | import cn.fciasth.zhihu.bean.Comment; 4 | import cn.fciasth.zhihu.bean.Message; 5 | import org.apache.ibatis.annotations.*; 6 | 7 | import java.util.List; 8 | 9 | @Mapper 10 | public interface MessageDAO { 11 | 12 | String TABLE_NAME = " message "; 13 | String INSERT_FIELDS = " from_id, to_id, content, has_read, conversation_id, created_date "; 14 | String SELECT_FIELDS = " id, " + INSERT_FIELDS; 15 | 16 | @Insert({"insert into ", TABLE_NAME, "(", INSERT_FIELDS, 17 | ") values (#{fromId},#{toId},#{content},#{hasRead},#{conversationId},#{createdDate})"}) 18 | int addMessage(Message message); 19 | 20 | @Select({"select ", SELECT_FIELDS, " from ", TABLE_NAME, " where conversation_id=#{conversationId} order by id desc limit #{offset}, #{limit}"}) 21 | List getConversationDetail(@Param("conversationId") String conversationId, 22 | @Param("offset") int offset, @Param("limit") int limit); 23 | 24 | @Select({"select count(id) from ", TABLE_NAME, " where has_read=0 and to_id=#{userId} and conversation_id=#{conversationId}"}) 25 | int getConvesationUnreadCount(@Param("userId") int userId, @Param("conversationId") String conversationId); 26 | 27 | @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 limit 0,100) tt group by conversation_id order by created_date desc limit #{offset}, #{limit}"}) 28 | List getConversationList(@Param("userId") int userId, 29 | @Param("offset") int offset, @Param("limit") int limit); 30 | } 31 | -------------------------------------------------------------------------------- /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/cn/fciasth/zhihu/async/handler/FollowHandler.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.async.handler; 2 | 3 | 4 | import cn.fciasth.zhihu.async.EventHandler; 5 | import cn.fciasth.zhihu.async.EventModel; 6 | import cn.fciasth.zhihu.async.EventType; 7 | import cn.fciasth.zhihu.bean.EntityType; 8 | import cn.fciasth.zhihu.bean.Message; 9 | import cn.fciasth.zhihu.bean.User; 10 | import cn.fciasth.zhihu.service.MessageService; 11 | import cn.fciasth.zhihu.service.UserService; 12 | import cn.fciasth.zhihu.util.CommonUtils; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.stereotype.Component; 15 | 16 | import java.util.Arrays; 17 | import java.util.Date; 18 | import java.util.List; 19 | 20 | @Component 21 | public class FollowHandler implements EventHandler { 22 | @Autowired 23 | MessageService messageService; 24 | 25 | @Autowired 26 | UserService userService; 27 | 28 | @Override 29 | public void doHandle(EventModel model) { 30 | Message message = new Message(); 31 | message.setFromId(CommonUtils.SYSTEM_USERID); 32 | message.setToId(model.getEntityOwnerId()); 33 | message.setCreatedDate(new Date()); 34 | User user = userService.getUser(model.getActorId()); 35 | 36 | if (model.getEntityType() == EntityType.ENTITY_QUESTION) { 37 | message.setContent("用户" + user.getName() 38 | + "关注了你的问题,http://127.0.0.1:8080/question/" + model.getEntityId()); 39 | } else if (model.getEntityType() == EntityType.ENTITY_USER) { 40 | message.setContent("用户" + user.getName() 41 | + "关注了你,http://127.0.0.1:8080/user/" + model.getActorId()); 42 | } 43 | 44 | messageService.addMessage(message); 45 | } 46 | 47 | @Override 48 | public List getSupportEventTypes() { 49 | return Arrays.asList(EventType.FOLLOW); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/service/LikeService.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.service; 2 | 3 | import cn.fciasth.zhihu.util.JedisAdapter; 4 | import cn.fciasth.zhihu.util.RedisKeyUtil; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Service; 7 | 8 | @Service 9 | public class LikeService { 10 | 11 | @Autowired 12 | private JedisAdapter jedisAdapter; 13 | 14 | public long getLikeCount(int entityType, int entityId){ 15 | String likeKey = RedisKeyUtil.getLikeKey(entityType,entityId); 16 | return jedisAdapter.scard(likeKey); 17 | } 18 | 19 | public long like(int userId, int entityType, int entityId){ 20 | String likeKey = RedisKeyUtil.getLikeKey(entityType,entityId); 21 | jedisAdapter.sadd(likeKey,String.valueOf(userId)); 22 | 23 | String disLikeKey = RedisKeyUtil.getDisLikeKey(entityType,entityId); 24 | jedisAdapter.srem(disLikeKey,String.valueOf(userId)); 25 | 26 | return jedisAdapter.scard(likeKey); 27 | } 28 | 29 | public long disLike(int userId, int entityType, int entityId){ 30 | String disLikeKey = RedisKeyUtil.getDisLikeKey(entityType,entityId); 31 | jedisAdapter.sadd(disLikeKey,String.valueOf(userId)); 32 | 33 | String likeKey = RedisKeyUtil.getLikeKey(entityType,entityId); 34 | jedisAdapter.srem(likeKey,String.valueOf(userId)); 35 | 36 | return jedisAdapter.scard(likeKey); 37 | } 38 | 39 | public int getLikeStatus(int userId, int entityType, int entityId){ 40 | String likeKey = RedisKeyUtil.getLikeKey(entityType,entityId); 41 | if(jedisAdapter.sismember(likeKey,String.valueOf(userId))){ 42 | return 1; 43 | } 44 | String disLikeKey = RedisKeyUtil.getDisLikeKey(entityType,entityId); 45 | if(jedisAdapter.sismember(disLikeKey,String.valueOf(userId))){ 46 | return -1; 47 | } 48 | return 0; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/bean/Question.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.bean; 2 | 3 | import org.springframework.data.annotation.Id; 4 | import org.springframework.data.elasticsearch.annotations.Document; 5 | 6 | import java.util.Date; 7 | 8 | @Document(indexName = "zhihu",type = "question") 9 | public class Question { 10 | @Id 11 | private int id; 12 | private String title; 13 | private String content; 14 | private Date createdDate; 15 | private int userId; 16 | private int commentCount; 17 | 18 | public int getId() { 19 | return id; 20 | } 21 | 22 | public void setId(int id) { 23 | this.id = id; 24 | } 25 | 26 | public String getTitle() { 27 | return title; 28 | } 29 | 30 | public void setTitle(String title) { 31 | this.title = title; 32 | } 33 | 34 | public String getContent() { 35 | return content; 36 | } 37 | 38 | public void setContent(String content) { 39 | this.content = content; 40 | } 41 | 42 | public Date getCreatedDate() { 43 | return createdDate; 44 | } 45 | 46 | public void setCreatedDate(Date createdDate) { 47 | this.createdDate = createdDate; 48 | } 49 | 50 | public int getUserId() { 51 | return userId; 52 | } 53 | 54 | public void setUserId(int userId) { 55 | this.userId = userId; 56 | } 57 | 58 | public int getCommentCount() { 59 | return commentCount; 60 | } 61 | 62 | public void setCommentCount(int commentCount) { 63 | this.commentCount = commentCount; 64 | } 65 | 66 | @Override 67 | public String toString() { 68 | return "Question{" + 69 | "id=" + id + 70 | ", title='" + title + '\'' + 71 | ", content='" + content + '\'' + 72 | ", createdDate=" + createdDate + 73 | ", userId=" + userId + 74 | ", commentCount=" + commentCount + 75 | '}'; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/config/DruidConfig.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.config; 2 | 3 | import com.alibaba.druid.pool.DruidDataSource; 4 | import com.alibaba.druid.support.http.StatViewServlet; 5 | import com.alibaba.druid.support.http.WebStatFilter; 6 | import org.springframework.boot.context.properties.ConfigurationProperties; 7 | import org.springframework.boot.web.servlet.FilterRegistrationBean; 8 | import org.springframework.boot.web.servlet.ServletRegistrationBean; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | 12 | import javax.sql.DataSource; 13 | import java.util.Arrays; 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | 17 | //导入druid数据源 18 | @Configuration 19 | public class DruidConfig { 20 | 21 | @ConfigurationProperties(prefix = "spring.datasource") 22 | @Bean 23 | public DataSource druid(){ 24 | return new DruidDataSource(); 25 | } 26 | 27 | //配置Druid的监控 28 | //1、配置一个管理后台的Servlet 29 | @Bean 30 | public ServletRegistrationBean statViewServlet(){ 31 | ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*"); 32 | Map initParams = new HashMap<>(); 33 | 34 | initParams.put("loginUsername","admin"); 35 | initParams.put("loginPassword","123456"); 36 | initParams.put("allow","");//默认就是允许所有访问 37 | initParams.put("deny","10.158.119.44"); 38 | 39 | bean.setInitParameters(initParams); 40 | return bean; 41 | } 42 | 43 | 44 | //2、配置一个web监控的filter 45 | @Bean 46 | public FilterRegistrationBean webStatFilter(){ 47 | FilterRegistrationBean bean = new FilterRegistrationBean(); 48 | bean.setFilter(new WebStatFilter()); 49 | 50 | Map initParams = new HashMap<>(); 51 | initParams.put("exclusions","*.js,*.css,/druid/*"); 52 | 53 | bean.setInitParameters(initParams); 54 | 55 | bean.setUrlPatterns(Arrays.asList("/*")); 56 | 57 | return bean; 58 | } 59 | } -------------------------------------------------------------------------------- /src/main/resources/static/scripts/main/site/detail.js: -------------------------------------------------------------------------------- 1 | (function (window, undefined) { 2 | var Action = Base.getClass('main.util.Action'); 3 | var Business = Base.getClass('main.util.Business'); 4 | 5 | Base.ready({ 6 | initialize: fInitialize, 7 | // 事件代理 8 | events: { 9 | 'click .js-like': fVote, 10 | 'click .js-dislike': fVote 11 | } 12 | }); 13 | 14 | function fInitialize() { 15 | var that = this; 16 | // 点击关注问题 17 | Business.followQuestion({ 18 | countEl: $('.js-user-count'), 19 | listEl: $('.js-user-list') 20 | }); 21 | } 22 | 23 | function fVote(oEvent) { 24 | var that = this; 25 | var oEl = $(oEvent.currentTarget); 26 | var oDv = oEl.closest('div.js-vote'); 27 | var sId = $.trim(oDv.attr('data-id')); 28 | var bLike = oEl.hasClass('js-like'); 29 | if (!sId) { 30 | return; 31 | } 32 | if (that.isVote) { 33 | return; 34 | } 35 | that.isVote = true; 36 | Action[bLike ? 'like' : 'dislike']({ 37 | commentId: sId, 38 | call: function (oResult) { 39 | // 调整样式 40 | oDv.find('.pressed').removeClass('pressed'); 41 | oDv.find(bLike ? '.js-like' : '.js-dislike').addClass('pressed'); 42 | // 更新数量 43 | oDv.closest('div.js-comment').find('span.js-voteCount').html(oResult.msg); 44 | }, 45 | error: function (oResult) { 46 | if (oResult.code === 999) { 47 | alert('请登录后再操作'); 48 | window.location.href = '/reglogin?next=' + window.decodeURIComponent(window.location.href); 49 | } else { 50 | alert('出现错误,请重试'); 51 | } 52 | }, 53 | always: function () { 54 | that.isVote = false; 55 | } 56 | }); 57 | } 58 | 59 | function fUnlike(oEvent) { 60 | var that = this; 61 | var oEl = $(oEvent.currentTarget); 62 | 63 | } 64 | 65 | })(window); -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/async/EventModel.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.async; 2 | 3 | import java.util.Map; 4 | 5 | import cn.fciasth.zhihu.async.EventType; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | 10 | public class EventModel { 11 | private EventType type; 12 | private int actorId; 13 | private int entityType; 14 | private int entityId; 15 | private int entityOwnerId; 16 | 17 | private Map exts = new HashMap(); 18 | 19 | public EventModel() { 20 | 21 | } 22 | 23 | public EventModel setExt(String key, String value) { 24 | exts.put(key, value); 25 | return this; 26 | } 27 | 28 | public EventModel(EventType type) { 29 | this.type = type; 30 | } 31 | 32 | public String getExt(String key) { 33 | return exts.get(key); 34 | } 35 | 36 | 37 | public EventType getType() { 38 | return type; 39 | } 40 | 41 | public EventModel setType(EventType type) { 42 | this.type = type; 43 | return this; 44 | } 45 | 46 | public int getActorId() { 47 | return actorId; 48 | } 49 | 50 | public EventModel setActorId(int actorId) { 51 | this.actorId = actorId; 52 | return this; 53 | } 54 | 55 | public int getEntityType() { 56 | return entityType; 57 | } 58 | 59 | public EventModel setEntityType(int entityType) { 60 | this.entityType = entityType; 61 | return this; 62 | } 63 | 64 | public int getEntityId() { 65 | return entityId; 66 | } 67 | 68 | public EventModel setEntityId(int entityId) { 69 | this.entityId = entityId; 70 | return this; 71 | } 72 | 73 | public int getEntityOwnerId() { 74 | return entityOwnerId; 75 | } 76 | 77 | public EventModel setEntityOwnerId(int entityOwnerId) { 78 | this.entityOwnerId = entityOwnerId; 79 | return this; 80 | } 81 | 82 | public Map getExts() { 83 | return exts; 84 | } 85 | 86 | public EventModel setExts(Map exts) { 87 | this.exts = exts; 88 | return this; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/controller/CommentController.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.controller; 2 | 3 | import cn.fciasth.zhihu.bean.Comment; 4 | import cn.fciasth.zhihu.bean.EntityType; 5 | import cn.fciasth.zhihu.bean.HostHolder; 6 | import cn.fciasth.zhihu.service.CommentService; 7 | import cn.fciasth.zhihu.service.QuestionService; 8 | import cn.fciasth.zhihu.util.CommonUtils; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.stereotype.Controller; 13 | import org.springframework.web.bind.annotation.RequestMapping; 14 | import org.springframework.web.bind.annotation.RequestMethod; 15 | import org.springframework.web.bind.annotation.RequestParam; 16 | import org.springframework.web.bind.annotation.RestController; 17 | 18 | import java.util.Date; 19 | 20 | @Controller 21 | public class CommentController { 22 | 23 | private static final Logger logger = LoggerFactory.getLogger(CommentController.class); 24 | 25 | @Autowired 26 | private CommentService commentService; 27 | 28 | @Autowired 29 | private HostHolder hostHolder; 30 | 31 | @Autowired 32 | private QuestionService questionService; 33 | 34 | @RequestMapping(value = "/addComment",method = RequestMethod.POST) 35 | public String addComment(@RequestParam("questionId") int questionId, 36 | @RequestParam("content") String content){ 37 | try { 38 | Comment comment = new Comment(); 39 | comment.setContent(content); 40 | comment.setEntityId(questionId); 41 | comment.setEntityType(EntityType.ENTITY_QUESTION); 42 | comment.setCreatedDate(new Date()); 43 | if(hostHolder.getUser() == null){ 44 | return "redirect:/reglogin"; 45 | }else{ 46 | comment.setUserId(hostHolder.getUser().getId()); 47 | } 48 | commentService.addComment(comment); 49 | int count = commentService.getCommentCount(comment.getEntityId(),comment.getEntityType()); 50 | questionService.updateCommentCount(comment.getEntityId(),count); 51 | }catch (Exception e){ 52 | logger.error("增加评论失败"+e.getMessage()); 53 | } 54 | return "redirect:/question/"+ questionId; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/util/CommonUtils.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.util; 2 | 3 | import cn.fciasth.zhihu.controller.IndexController; 4 | import com.alibaba.fastjson.JSON; 5 | import com.alibaba.fastjson.JSONObject; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.security.MessageDigest; 10 | import java.util.Map; 11 | 12 | public class CommonUtils { 13 | 14 | private static final Logger logger = LoggerFactory.getLogger(CommonUtils.class); 15 | 16 | public static int ANONYMOUS_USERID = 3; 17 | 18 | public static int SYSTEM_USERID = 4; 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 | -------------------------------------------------------------------------------- /src/main/resources/Spider.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | # Created on 2018-06-05 12:25:15 4 | # Project: v2ex 5 | 6 | from pyspider.libs.base_handler import * 7 | import MySQLdb 8 | import random 9 | 10 | class Handler(BaseHandler): 11 | crawl_config = { 12 | } 13 | 14 | def __init__(self): 15 | # 数据库连接所需参数 16 | self.db = MySQLdb.connect('193.112.108.187','zhihu',password,'zhihu',charset='utf8') 17 | 18 | def add_question(self, title, content): 19 | 20 | cursor = self.db.cursor() 21 | sql = 'insert into question(title, content, user_id, created_date, comment_count) values ("%s" ,"%s" ,%d, now(), 0)' % (title, content, random.randint(2,8)) 22 | print sql 23 | cursor.execute(sql) 24 | print cursor.lastrowid 25 | self.db.commit() 26 | 27 | 28 | @every(minutes=24 * 60) 29 | def on_start(self): 30 | self.crawl('https://www.v2ex.com/', callback=self.index_page, validate_cert=False) 31 | 32 | @config(age=10 * 24 * 60 * 60) 33 | def index_page(self, response): 34 | for each in response.doc('a[href^="https://www.v2ex.com/?tab="]').items(): 35 | self.crawl(each.attr.href, callback=self.tab_page, validate_cert=False) 36 | 37 | @config(priority=2) 38 | def tab_page(self, response): 39 | for each in response.doc('a[href^="https://www.v2ex.com/go/"]').items(): 40 | self.crawl(each.attr.href, callback=self.board_page, validate_cert=False) 41 | 42 | @config(priority=2) 43 | def board_page(self, response): 44 | for each in response.doc('a[href^="https://www.v2ex.com/t/"]').items(): 45 | url = each.attr.href 46 | if url.find('#reply') > 0: 47 | url = url[0:url.find('#')] 48 | self.crawl(url, callback=self.detail_page, validate_cert=False) 49 | for each in response.doc('a.page_normal').items(): 50 | self.crawl(each.attr.href, callback=self.board_page, validate_cert=False) 51 | 52 | 53 | @config(priority=2) 54 | def detail_page(self, response): 55 | title = response.doc('h1').text() 56 | content = response.doc('div.topic_content').text().replace('"','\\"') 57 | self.add_question(title,content) 58 | return { 59 | "url": response.url, 60 | "title": response.doc('title').text(), 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/interceptor/PassportInterceptor.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.interceptor; 2 | 3 | import cn.fciasth.zhihu.bean.HostHolder; 4 | import cn.fciasth.zhihu.bean.LoginTicket; 5 | import cn.fciasth.zhihu.bean.User; 6 | import cn.fciasth.zhihu.dao.LoginTicketDAO; 7 | import cn.fciasth.zhihu.dao.UserDAO; 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 | @Component 20 | public class PassportInterceptor implements HandlerInterceptor { 21 | 22 | @Autowired 23 | private LoginTicketDAO loginTicketDAO; 24 | 25 | @Autowired 26 | private UserDAO userDAO; 27 | 28 | @Autowired 29 | private HostHolder hostHolder; 30 | 31 | @Override 32 | public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception { 33 | String ticket = null; 34 | if (httpServletRequest.getCookies() != null) { 35 | for (Cookie cookie : httpServletRequest.getCookies()) { 36 | if (cookie.getName().equals("ticket")) { 37 | ticket = cookie.getValue(); 38 | break; 39 | } 40 | } 41 | } 42 | 43 | if (ticket != null) { 44 | LoginTicket loginTicket = loginTicketDAO.selectByTicket(ticket); 45 | if (loginTicket == null || loginTicket.getExpired().before(new Date()) || loginTicket.getStatus() != 0) { 46 | return true; 47 | } 48 | 49 | User user = userDAO.selectById(loginTicket.getUserId()); 50 | hostHolder.setUser(user); 51 | } 52 | return true; 53 | } 54 | 55 | @Override 56 | public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { 57 | if (modelAndView != null && hostHolder.getUser() != null) { 58 | modelAndView.addObject("user", hostHolder.getUser()); 59 | } 60 | } 61 | 62 | @Override 63 | public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { 64 | hostHolder.clear(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/controller/SearchController.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.controller; 2 | 3 | import cn.fciasth.zhihu.bean.EntityType; 4 | import cn.fciasth.zhihu.bean.Question; 5 | import cn.fciasth.zhihu.service.FollowService; 6 | import cn.fciasth.zhihu.service.QuestionService; 7 | import cn.fciasth.zhihu.service.SearchService; 8 | import cn.fciasth.zhihu.service.UserService; 9 | import cn.fciasth.zhihu.vo.ViewObject; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.stereotype.Controller; 14 | import org.springframework.ui.Model; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | import org.springframework.web.bind.annotation.RequestMethod; 17 | import org.springframework.web.bind.annotation.RequestParam; 18 | 19 | import java.util.ArrayList; 20 | import java.util.Date; 21 | import java.util.List; 22 | 23 | 24 | @Controller 25 | public class SearchController { 26 | private static final Logger logger = LoggerFactory.getLogger(SearchController.class); 27 | @Autowired 28 | SearchService searchService; 29 | 30 | @Autowired 31 | FollowService followService; 32 | 33 | @Autowired 34 | UserService userService; 35 | 36 | @Autowired 37 | QuestionService questionService; 38 | 39 | @RequestMapping(path = {"/search"}, method = {RequestMethod.GET}) 40 | public String search(Model model, @RequestParam("q") String keyword, 41 | @RequestParam(value = "p",defaultValue = "1") int p) { 42 | try { 43 | int offset = (p-1)*10; 44 | 45 | List questionList = searchService.testSearch(keyword,offset,10); 46 | List vos = new ArrayList<>(); 47 | for (Question question : questionList) { 48 | Question q = questionService.selectById(question.getId()); 49 | ViewObject vo = new ViewObject(); 50 | if (question.getContent() != null) { 51 | q.setContent(question.getContent()); 52 | } 53 | if (question.getTitle() != null) { 54 | q.setTitle(question.getTitle()); 55 | } 56 | vo.set("question", q); 57 | vo.set("followCount", followService.getFollowerCount(EntityType.ENTITY_QUESTION, question.getId())); 58 | vo.set("user", userService.getUser(q.getUserId())); 59 | vos.add(vo); 60 | } 61 | model.addAttribute("vos", vos); 62 | model.addAttribute("keyword", keyword); 63 | } catch (Exception e) { 64 | logger.error("搜索失败" + e.getMessage()); 65 | } 66 | return "result"; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/controller/LikeController.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.controller; 2 | 3 | import cn.fciasth.zhihu.async.EventModel; 4 | import cn.fciasth.zhihu.async.EventProducer; 5 | import cn.fciasth.zhihu.async.EventType; 6 | import cn.fciasth.zhihu.bean.Comment; 7 | import cn.fciasth.zhihu.bean.EntityType; 8 | import cn.fciasth.zhihu.bean.HostHolder; 9 | import cn.fciasth.zhihu.service.CommentService; 10 | import cn.fciasth.zhihu.service.LikeService; 11 | import cn.fciasth.zhihu.util.CommonUtils; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.stereotype.Controller; 16 | import org.springframework.web.bind.annotation.RequestMapping; 17 | import org.springframework.web.bind.annotation.RequestMethod; 18 | import org.springframework.web.bind.annotation.RequestParam; 19 | import org.springframework.web.bind.annotation.ResponseBody; 20 | 21 | @Controller 22 | public class LikeController { 23 | 24 | private static final Logger logger = LoggerFactory.getLogger(LikeController.class); 25 | 26 | @Autowired 27 | private LikeService likeService; 28 | 29 | @Autowired 30 | private HostHolder hostHolder; 31 | 32 | @Autowired 33 | private CommentService commentService; 34 | 35 | @Autowired 36 | private EventProducer eventProducer; 37 | 38 | @RequestMapping(value = "/like",method = RequestMethod.POST) 39 | @ResponseBody 40 | public String like(@RequestParam("commentId") int commentId){ 41 | if(hostHolder.getUser() == null){ 42 | return CommonUtils.getJSONString(999); 43 | } 44 | Comment comment = commentService.getCommentById(commentId); 45 | 46 | eventProducer.fireEvent(new EventModel(EventType.LIKE) 47 | .setActorId(hostHolder.getUser().getId()).setEntityId(commentId) 48 | .setEntityType(EntityType.ENTITY_COMMENT).setEntityOwnerId(comment.getUserId()) 49 | .setExt("questionId", String.valueOf(comment.getEntityId()))); 50 | long likeCount = likeService.like(hostHolder.getUser().getId(), EntityType.ENTITY_COMMENT, commentId); 51 | return CommonUtils.getJSONString(0,String.valueOf(likeCount)); 52 | } 53 | 54 | @RequestMapping(value = "/dislike",method = RequestMethod.POST) 55 | @ResponseBody 56 | public String disLike(@RequestParam("commentId") int commentId){ 57 | if(hostHolder.getUser() == null){ 58 | return CommonUtils.getJSONString(999); 59 | } 60 | long disLikeCount = likeService.disLike(hostHolder.getUser().getId(), EntityType.ENTITY_COMMENT, commentId); 61 | System.out.println("dislike"); 62 | return CommonUtils.getJSONString(0,String.valueOf(disLikeCount)); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/resources/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 知乎 - 与世界分享你的知识、经验和见解 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |
20 |

21 |

22 | 与世界分享你的知识、经验和见解 23 |

24 |

25 |
26 |
27 | 52 |
53 |
54 |
55 | 56 | -------------------------------------------------------------------------------- /src/test/java/cn/fciasth/zhihu/ZhihuApplicationTests.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu; 2 | 3 | import cn.fciasth.zhihu.bean.Question; 4 | import cn.fciasth.zhihu.bean.User; 5 | import cn.fciasth.zhihu.dao.QuestionDAO; 6 | import cn.fciasth.zhihu.dao.QuestionRepository; 7 | import cn.fciasth.zhihu.dao.UserDAO; 8 | import org.elasticsearch.action.search.SearchRequestBuilder; 9 | import org.elasticsearch.client.Client; 10 | import org.elasticsearch.client.transport.TransportClient; 11 | import org.elasticsearch.common.transport.InetSocketTransportAddress; 12 | import org.elasticsearch.index.mapper.ObjectMapper; 13 | import org.elasticsearch.index.query.QueryBuilders; 14 | import org.junit.Assert; 15 | import org.junit.Test; 16 | import org.junit.runner.RunWith; 17 | import org.springframework.beans.factory.annotation.Autowired; 18 | import org.springframework.boot.test.context.SpringBootTest; 19 | import org.springframework.test.context.junit4.SpringRunner; 20 | 21 | import java.net.InetAddress; 22 | import java.util.Date; 23 | import java.util.List; 24 | import java.util.Random; 25 | 26 | @RunWith(SpringRunner.class) 27 | @SpringBootTest 28 | public class ZhihuApplicationTests { 29 | 30 | @Autowired 31 | private UserDAO userDAO; 32 | 33 | @Autowired 34 | private QuestionDAO questionDAO; 35 | 36 | @Autowired 37 | private QuestionRepository questionRepository; 38 | 39 | @Test 40 | public void contextLoads() { 41 | /*Random random = new Random(); 42 | for (int i = 1;i<=10 ;i++){ 43 | User user = new User(); 44 | user.setHeadUrl(String.format("http://images.nowcoder.com/head/%dt.png",random.nextInt(1000))); 45 | user.setName(String.format("USER%d",i)); 46 | user.setPassword(""); 47 | user.setSalt(""); 48 | userDAO.addUser(user); 49 | user.setPassword("xx"); 50 | userDAO.updatePassword(user); 51 | }*/ 52 | User user = userDAO.selectById(24); 53 | user.setPassword("xxxxx"); 54 | userDAO.updatePassword(user); 55 | userDAO.deleteById(22); 56 | System.out.println(userDAO.selectById(23)); 57 | } 58 | 59 | 60 | 61 | @Test 62 | public void testElastic(){ 63 | 64 | List questions = questionDAO.selectLatestQuestions(0, 10, 160); 65 | for (Question question:questions 66 | ) { 67 | questionRepository.index(question); 68 | } 69 | } 70 | 71 | @Test 72 | public void testElastic01(){ 73 | for (Question question:questionRepository.findQuestionsByContentLikeOrTitleLike("手机","手机") 74 | ) { 75 | System.out.println(question); 76 | } 77 | } 78 | 79 | 80 | @Test 81 | public void test01(){ 82 | // for (int i = 1;i<=10 ;i++){ 83 | // Question question = new Question(); 84 | // question.setTitle(String.format("TITLE%d",i)); 85 | // Date date = new Date(); 86 | // date.setTime(date.getTime()+1000*3600*i); 87 | // question.setCreatedDate(date); 88 | // question.setCommentCount(i); 89 | // question.setUserId(i+1); 90 | // question.setContent(String.format("BLABALBALABLABALAB %d",i)); 91 | // questionDAO.addQuestion(question); 92 | // } 93 | System.out.println(questionDAO.selectLatestQuestions(0,0,5)); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/async/EventConsumer.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.async; 2 | 3 | import cn.fciasth.zhihu.util.JedisAdapter; 4 | import cn.fciasth.zhihu.util.RedisKeyUtil; 5 | import com.alibaba.fastjson.JSON; 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 | import sun.java2d.pipe.hw.AccelDeviceEventNotifier; 15 | 16 | import java.awt.*; 17 | import java.util.ArrayList; 18 | import java.util.HashMap; 19 | import java.util.List; 20 | import java.util.Map; 21 | import java.util.concurrent.BlockingQueue; 22 | 23 | @Service 24 | public class EventConsumer implements InitializingBean, ApplicationContextAware { 25 | private static final Logger logger = LoggerFactory.getLogger(EventConsumer.class); 26 | private Map> config = new HashMap>(); 27 | private ApplicationContext applicationContext; 28 | 29 | @Autowired 30 | JedisAdapter jedisAdapter; 31 | 32 | @Override 33 | public void afterPropertiesSet() throws Exception { 34 | Map beans = applicationContext.getBeansOfType(EventHandler.class); 35 | if (beans != null) { 36 | for (Map.Entry entry : beans.entrySet()) { 37 | List eventTypes = entry.getValue().getSupportEventTypes(); 38 | 39 | for (EventType type : eventTypes) { 40 | if (!config.containsKey(type)) { 41 | config.put(type, new ArrayList()); 42 | } 43 | config.get(type).add(entry.getValue()); 44 | } 45 | } 46 | } 47 | 48 | Thread thread = new Thread(new Runnable() { 49 | @Override 50 | public void run() { 51 | while(true) { 52 | String key = RedisKeyUtil.getEventQueueKey(); 53 | List events = jedisAdapter.brpop(0, key); 54 | 55 | for (String message : events) { 56 | if (message.equals(key)) { 57 | continue; 58 | } 59 | 60 | EventModel eventModel = JSON.parseObject(message, EventModel.class); 61 | if (!config.containsKey(eventModel.getType())) { 62 | logger.error("不能识别的事件"); 63 | continue; 64 | } 65 | 66 | for (EventHandler handler : config.get(eventModel.getType())) { 67 | handler.doHandle(eventModel); 68 | } 69 | } 70 | } 71 | } 72 | }); 73 | thread.start(); 74 | } 75 | 76 | @Override 77 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 78 | this.applicationContext = applicationContext; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/controller/LoginController.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.controller; 2 | 3 | import cn.fciasth.zhihu.service.UserService; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Controller; 8 | import org.springframework.ui.Model; 9 | import org.springframework.web.bind.annotation.CookieValue; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RequestMethod; 12 | import org.springframework.web.bind.annotation.RequestParam; 13 | 14 | import javax.servlet.http.Cookie; 15 | import javax.servlet.http.HttpServletResponse; 16 | import java.util.Map; 17 | 18 | @Controller 19 | public class LoginController { 20 | 21 | private static final Logger logger = LoggerFactory.getLogger(LoginController.class); 22 | 23 | @Autowired 24 | private UserService userService; 25 | 26 | @RequestMapping(value = "/login",method = RequestMethod.POST) 27 | public String login(@RequestParam("username") String username, 28 | @RequestParam("password") String password, 29 | @RequestParam(value = "rememberme",defaultValue = "false") boolean rememberme, 30 | HttpServletResponse response, 31 | Model model){ 32 | try { 33 | Map map = userService.login(username,password); 34 | if(map.containsKey("ticket")){ 35 | Cookie cookie = new Cookie("ticket",map.get("ticket")); 36 | cookie.setPath("/"); 37 | if (rememberme) { 38 | cookie.setMaxAge(3600*24*5); 39 | } 40 | response.addCookie(cookie); 41 | return "redirect:/"; 42 | }else{ 43 | model.addAttribute("msg",map.get("msg")); 44 | return "login"; 45 | } 46 | 47 | }catch (Exception e){ 48 | logger.error("登录异常:"+e.getMessage()); 49 | return "index"; 50 | } 51 | 52 | } 53 | 54 | @RequestMapping(value = "/reg",method = RequestMethod.POST) 55 | public String reg(@RequestParam("username") String username, 56 | @RequestParam("password") String password, 57 | HttpServletResponse response, 58 | Model model){ 59 | 60 | try { 61 | Map map = userService.register(username,password); 62 | if(map.containsKey("ticket")){ 63 | Cookie cookie = new Cookie("ticket",map.get("ticket")); 64 | cookie.setPath("/"); 65 | 66 | response.addCookie(cookie); 67 | return "redirect:/"; 68 | }else{ 69 | model.addAttribute("msg",map.get("msg")); 70 | return "login"; 71 | } 72 | }catch (Exception e){ 73 | logger.error("注册异常:"+e.getMessage()); 74 | return "index"; 75 | } 76 | } 77 | 78 | @RequestMapping(value = "/reglogin",method = RequestMethod.GET) 79 | public String regLogin(Model model){ 80 | return "login"; 81 | } 82 | 83 | @RequestMapping(value = "/logout",method = RequestMethod.GET) 84 | public String logout(@CookieValue("ticket") String ticket){ 85 | userService.logout(ticket); 86 | return "redirect:/"; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/service/UserService.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.service; 2 | 3 | import cn.fciasth.zhihu.bean.LoginTicket; 4 | import cn.fciasth.zhihu.bean.User; 5 | import cn.fciasth.zhihu.dao.LoginTicketDAO; 6 | import cn.fciasth.zhihu.dao.UserDAO; 7 | import cn.fciasth.zhihu.util.CommonUtils; 8 | import org.apache.commons.lang.StringUtils; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.stereotype.Service; 11 | import java.util.*; 12 | 13 | @Service 14 | public class UserService { 15 | 16 | @Autowired 17 | private UserDAO userDAO; 18 | 19 | @Autowired 20 | private LoginTicketDAO loginTicketDAO; 21 | 22 | public User getUser(int id){ 23 | return userDAO.selectById(id); 24 | } 25 | 26 | public User getUserByName(String name){ 27 | return userDAO.selectByName(name); 28 | } 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 | user = new User(); 47 | user.setName(username); 48 | user.setSalt(UUID.randomUUID().toString().substring(0,5)); 49 | user.setHeadUrl(String.format("http://images.nowcoder.com/head/%dt.png",new Random().nextInt(1000))); 50 | user.setPassword(CommonUtils.MD5(password+user.getSalt())); 51 | userDAO.addUser(user); 52 | String ticket = addLoginTicket(user.getId()); 53 | map.put("ticket",ticket); 54 | return map; 55 | } 56 | 57 | public Map login(String username, String password) { 58 | Map map = new HashMap(); 59 | if(StringUtils.isBlank(username)){ 60 | map.put("msg","用户名不能为空"); 61 | return map; 62 | } 63 | if(StringUtils.isBlank(password)){ 64 | map.put("msg","密码不能为空"); 65 | return map; 66 | } 67 | User user = userDAO.selectByName(username); 68 | if(user == null){ 69 | map.put("msg","该用户不存在"); 70 | return map; 71 | } 72 | 73 | if(!CommonUtils.MD5(password+user.getSalt()).equals(user.getPassword())){ 74 | map.put("msg","用户名或密码错误"); 75 | return map; 76 | } 77 | 78 | String ticket = addLoginTicket(user.getId()); 79 | map.put("ticket",ticket); 80 | 81 | return map; 82 | } 83 | 84 | public String addLoginTicket(int userId){ 85 | LoginTicket loginTicket = new LoginTicket(); 86 | loginTicket.setUserId(userId); 87 | loginTicket.setStatus(0); 88 | Date date = new Date(); 89 | date.setTime(date.getTime()+3600*24*1000*10); 90 | loginTicket.setExpired(date); 91 | loginTicket.setTicket(UUID.randomUUID().toString().replaceAll("-","")); 92 | loginTicketDAO.addTicket(loginTicket); 93 | return loginTicket.getTicket(); 94 | } 95 | 96 | public void logout(String ticket){ 97 | loginTicketDAO.updateStatus(ticket,1); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/resources/static/scripts/main/util/business.js: -------------------------------------------------------------------------------- 1 | (function (window, undefined) { 2 | var Business = Base.createClass('main.util.Business'); 3 | var Action = Base.getClass('main.util.Action'); 4 | 5 | $.extend(Business, { 6 | followUser: fFollowUser, 7 | followQuestion: fFollowQuestion 8 | }); 9 | 10 | function fFollowUser() { 11 | $(document).on('click', '.js-follow-user', function (oEvent) { 12 | var oEl = $(oEvent.currentTarget); 13 | var sId = $.trim(oEl.attr('data-id')); 14 | if (!sId) { 15 | return; 16 | } 17 | // 禁止频繁点击 18 | if (oEl.attr('data-limit')) { 19 | return; 20 | } 21 | oEl.attr('data-limit', '1'); 22 | var bFollow = oEl.attr('data-status') === '1'; 23 | Action[bFollow ? 'unFollowUser' : 'followUser']({ 24 | userId: sId, 25 | call: function (oResult) { 26 | // 修改标记位 27 | oEl.attr('data-status', bFollow ? '0' : '1'); 28 | // 按钮颜色 29 | oEl.removeClass('zg-btn-follow zg-btn-unfollow').addClass(bFollow ? 'zg-btn-follow' : 'zg-btn-unfollow'); 30 | // 文字 31 | oEl.html(bFollow ? '关注' : '取消关注'); 32 | }, 33 | error: function (oResult) { 34 | alert('出现错误,请重试'); 35 | }, 36 | always: function () { 37 | oEl.removeAttr('data-limit'); 38 | } 39 | }); 40 | }); 41 | } 42 | 43 | function fFollowQuestion(oConf) { 44 | var that = this; 45 | var oCountEl = $(oConf.countEl); 46 | var oListEl = $(oConf.listEl); 47 | $(document).on('click', '.js-follow-question', function (oEvent) { 48 | var oEl = $(oEvent.currentTarget); 49 | var sId = $.trim(oEl.attr('data-id')); 50 | if (!sId) { 51 | return; 52 | } 53 | // 禁止频繁点击 54 | if (oEl.attr('data-limit')) { 55 | return; 56 | } 57 | oEl.attr('data-limit', '1'); 58 | var bFollow = oEl.attr('data-status') === '1'; 59 | Action[bFollow ? 'unFollowQuestion' : 'followQuestion']({ 60 | questionId: sId, 61 | call: function (oResult) { 62 | // 修改标记位 63 | oEl.attr('data-status', bFollow ? '0' : '1'); 64 | // 按钮颜色 65 | oEl.removeClass('zg-btn-white zg-btn-green').addClass(bFollow ? 'zg-btn-green' : 'zg-btn-white'); 66 | // 文字 67 | oEl.html(bFollow ? '关注问题' : '取消关注'); 68 | // 修改数量 69 | oCountEl.html(oResult.count); 70 | if (bFollow) { 71 | // 移除用户 72 | oListEl.find('.js-user-' + oResult.id).remove(); 73 | } else { 74 | // 显示用户 75 | oListEl.prepend(''); 76 | } 77 | }, 78 | error: function (oResult) { 79 | alert('出现错误,请重试'); 80 | }, 81 | always: function () { 82 | oEl.removeAttr('data-limit'); 83 | } 84 | }); 85 | }); 86 | } 87 | })(window); -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/controller/IndexController.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.controller; 2 | 3 | import cn.fciasth.zhihu.bean.EntityType; 4 | import cn.fciasth.zhihu.bean.HostHolder; 5 | import cn.fciasth.zhihu.bean.Question; 6 | import cn.fciasth.zhihu.bean.User; 7 | import cn.fciasth.zhihu.service.CommentService; 8 | import cn.fciasth.zhihu.service.FollowService; 9 | import cn.fciasth.zhihu.service.QuestionService; 10 | import cn.fciasth.zhihu.service.UserService; 11 | import cn.fciasth.zhihu.vo.ViewObject; 12 | import com.sun.org.apache.xpath.internal.operations.Mod; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.stereotype.Controller; 17 | import org.springframework.ui.Model; 18 | import org.springframework.web.bind.annotation.*; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | @Controller 24 | public class IndexController { 25 | 26 | private static final Logger logger = LoggerFactory.getLogger(IndexController.class); 27 | 28 | @Autowired 29 | private UserService userService; 30 | 31 | @Autowired 32 | private QuestionService questionService; 33 | 34 | @Autowired 35 | private CommentService commentService; 36 | 37 | @Autowired 38 | private FollowService followService; 39 | 40 | @Autowired 41 | private HostHolder hostHolder; 42 | 43 | @RequestMapping(value = {"/user/{userId}"},method = RequestMethod.GET) 44 | public String userIndex(Model model, @PathVariable("userId") int userId){ 45 | model.addAttribute("vos",getQuestions(userId,0,10)); 46 | 47 | User user = userService.getUser(userId); 48 | ViewObject vo = new ViewObject(); 49 | vo.set("user", user); 50 | vo.set("commentCount", commentService.getUserCommentCount(userId)); 51 | vo.set("followerCount", followService.getFollowerCount(EntityType.ENTITY_USER, userId)); 52 | vo.set("followeeCount", followService.getFolloweeCount(userId, EntityType.ENTITY_USER)); 53 | if (hostHolder.getUser() != null) { 54 | vo.set("followed", followService.isFollower(hostHolder.getUser().getId(), EntityType.ENTITY_USER, userId)); 55 | } else { 56 | vo.set("followed", false); 57 | } 58 | model.addAttribute("profileUser", vo); 59 | return "profile"; 60 | // return "index"; 61 | } 62 | 63 | @RequestMapping(value = {"/","/index"},method = RequestMethod.GET) 64 | public String index(Model model, @RequestParam(value = "p",defaultValue = "1") int p){ 65 | int offset = (p-1)*10; 66 | model.addAttribute("vos",getQuestions(0,offset,10)); 67 | 68 | if(questionService.getLatestQuestions(0, offset+10, 10).size()==0){ 69 | model.addAttribute("end",1); 70 | }else { 71 | model.addAttribute("end",0); 72 | } 73 | model.addAttribute("page",p); 74 | return "index"; 75 | } 76 | 77 | public List getQuestions(int userId,int offset,int limit){ 78 | List questions = questionService.getLatestQuestions(userId, offset, limit); 79 | List vos = new ArrayList(); 80 | for (Question question: questions 81 | ) { 82 | ViewObject vo = new ViewObject(); 83 | if(question.getContent().length()>150){ 84 | question.setContent(question.getContent().substring(0,150)); 85 | } 86 | vo.set("question",question); 87 | vo.set("user",userService.getUser(question.getUserId())); 88 | vo.set("followCount", followService.getFollowerCount(EntityType.ENTITY_QUESTION, question.getId())); 89 | vos.add(vo); 90 | } 91 | return vos; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | cn.fciasth 7 | zhihu 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | zhihu 12 | a mini zhihu 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.0.2.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | 26 | 27 | 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-data-elasticsearch 32 | 33 | 34 | 35 | 36 | javax.mail 37 | mail 38 | 1.4.7 39 | 40 | 41 | 42 | 43 | redis.clients 44 | jedis 45 | 2.9.0 46 | 47 | 48 | 49 | 50 | com.alibaba 51 | fastjson 52 | 1.2.47 53 | 54 | 55 | 56 | 57 | commons-lang 58 | commons-lang 59 | 2.6 60 | 61 | 62 | 63 | org.springframework.boot 64 | spring-boot-starter-aop 65 | 66 | 67 | org.springframework.boot 68 | spring-boot-starter-jdbc 69 | 70 | 71 | org.springframework.boot 72 | spring-boot-starter-thymeleaf 73 | 74 | 75 | org.springframework.boot 76 | spring-boot-starter-web 77 | 78 | 79 | org.mybatis.spring.boot 80 | mybatis-spring-boot-starter 81 | 1.3.2 82 | 83 | 84 | 85 | org.springframework.boot 86 | spring-boot-devtools 87 | runtime 88 | 89 | 90 | mysql 91 | mysql-connector-java 92 | runtime 93 | 94 | 95 | org.springframework.boot 96 | spring-boot-starter-test 97 | test 98 | 99 | 100 | 101 | 102 | com.alibaba 103 | druid 104 | 1.1.9 105 | 106 | 107 | 108 | 109 | 110 | 111 | org.springframework.boot 112 | spring-boot-maven-plugin 113 | 114 | 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/controller/QuestionController.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.controller; 2 | 3 | import cn.fciasth.zhihu.bean.*; 4 | import cn.fciasth.zhihu.service.*; 5 | import cn.fciasth.zhihu.util.CommonUtils; 6 | import cn.fciasth.zhihu.vo.ViewObject; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.stereotype.Controller; 11 | import org.springframework.ui.Model; 12 | import org.springframework.web.bind.annotation.*; 13 | 14 | import java.util.ArrayList; 15 | import java.util.Date; 16 | import java.util.List; 17 | 18 | @Controller 19 | public class QuestionController { 20 | 21 | private static final Logger logger = LoggerFactory.getLogger(IndexController.class); 22 | 23 | @Autowired 24 | private QuestionService questionService; 25 | 26 | @Autowired 27 | private HostHolder hostHolder; 28 | 29 | @Autowired 30 | private UserService userService; 31 | 32 | @Autowired 33 | private CommentService commentService; 34 | 35 | @Autowired 36 | private LikeService likeService; 37 | 38 | @Autowired 39 | private FollowService followService; 40 | 41 | @RequestMapping(value = "/question/add",method = RequestMethod.POST) 42 | @ResponseBody 43 | public String addQuestion(@RequestParam("title") String title, 44 | @RequestParam("content") String content){ 45 | try { 46 | Question question = new Question(); 47 | question.setContent(content); 48 | question.setTitle(title); 49 | question.setCreatedDate(new Date()); 50 | question.setCommentCount(0); 51 | if(hostHolder.getUser()==null){ 52 | return CommonUtils.getJSONString(999); 53 | }else{ 54 | question.setUserId(hostHolder.getUser().getId()); 55 | } 56 | if(questionService.addQuestion(question)>0){ 57 | return CommonUtils.getJSONString(0); 58 | } 59 | }catch (Exception e){ 60 | logger.error("发表问题失败"+e.getMessage()); 61 | } 62 | return CommonUtils.getJSONString(1,"发表问题失败"); 63 | } 64 | 65 | @RequestMapping(value = "/question/{qid}", method = {RequestMethod.GET}) 66 | public String questionDetail(Model model, @PathVariable("qid") int qid) { 67 | Question question = questionService.selectById(qid); 68 | model.addAttribute("question", question); 69 | 70 | List commentList = commentService.selectCommentsByEntity(qid, EntityType.ENTITY_QUESTION); 71 | List comments = new ArrayList(); 72 | for (Comment comment : commentList) { 73 | ViewObject vo = new ViewObject(); 74 | vo.set("comment", comment); 75 | if (hostHolder.getUser() == null) { 76 | System.out.println("用户不存在"); 77 | vo.set("liked", 0); 78 | } else { 79 | vo.set("liked", likeService.getLikeStatus(hostHolder.getUser().getId(), EntityType.ENTITY_COMMENT, comment.getId())); 80 | } 81 | 82 | vo.set("likeCount", likeService.getLikeCount(EntityType.ENTITY_COMMENT, comment.getId())); 83 | vo.set("user", userService.getUser(comment.getUserId())); 84 | comments.add(vo); 85 | } 86 | 87 | model.addAttribute("comments", comments); 88 | 89 | List followUsers = new ArrayList(); 90 | // 获取关注的用户信息 91 | List users = followService.getFollowers(EntityType.ENTITY_QUESTION, qid, 20); 92 | for (Integer userId : users) { 93 | ViewObject vo = new ViewObject(); 94 | User u = userService.getUser(userId); 95 | if (u == null) { 96 | continue; 97 | } 98 | vo.set("name", u.getName()); 99 | vo.set("headUrl", u.getHeadUrl()); 100 | vo.set("id", u.getId()); 101 | followUsers.add(vo); 102 | } 103 | model.addAttribute("followUsers", followUsers); 104 | if (hostHolder.getUser() != null) { 105 | model.addAttribute("followed", followService.isFollower(hostHolder.getUser().getId(), EntityType.ENTITY_QUESTION, qid)); 106 | } else { 107 | model.addAttribute("followed", false); 108 | } 109 | 110 | return "detail"; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/service/FollowService.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.service; 2 | 3 | import cn.fciasth.zhihu.util.JedisAdapter; 4 | import cn.fciasth.zhihu.util.RedisKeyUtil; 5 | import org.springframework.beans.factory.ObjectFactory; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | import redis.clients.jedis.Jedis; 9 | import redis.clients.jedis.Response; 10 | import redis.clients.jedis.Transaction; 11 | 12 | import java.util.ArrayList; 13 | import java.util.Date; 14 | import java.util.List; 15 | import java.util.Set; 16 | 17 | 18 | @Service 19 | public class FollowService { 20 | @Autowired 21 | JedisAdapter jedisAdapter; 22 | 23 | /** 24 | * 用户关注了某个实体,可以关注问题,关注用户,关注评论等任何实体 25 | * @param userId 26 | * @param entityType 27 | * @param entityId 28 | * @return 29 | */ 30 | public boolean follow(int userId, int entityType, int entityId) { 31 | String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityId); 32 | String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType); 33 | Date date = new Date(); 34 | // 实体的粉丝增加当前用户 35 | Jedis jedis = jedisAdapter.getJedis(); 36 | Transaction tx = jedisAdapter.multi(jedis); 37 | tx.zadd(followerKey, date.getTime(), String.valueOf(userId)); 38 | // 当前用户对这类实体关注+1 39 | tx.zadd(followeeKey, date.getTime(), String.valueOf(entityId)); 40 | List ret = jedisAdapter.exec(tx, jedis); 41 | return ret.size() == 2 && (Long) ret.get(0) > 0 && (Long) ret.get(1) > 0; 42 | } 43 | 44 | /** 45 | * 取消关注 46 | * @param userId 47 | * @param entityType 48 | * @param entityId 49 | * @return 50 | */ 51 | public boolean unfollow(int userId, int entityType, int entityId) { 52 | String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityId); 53 | String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType); 54 | Date date = new Date(); 55 | Jedis jedis = jedisAdapter.getJedis(); 56 | Transaction tx = jedisAdapter.multi(jedis); 57 | // 实体的粉丝增加当前用户 58 | tx.zrem(followerKey, String.valueOf(userId)); 59 | // 当前用户对这类实体关注-1 60 | tx.zrem(followeeKey, String.valueOf(entityId)); 61 | List ret = jedisAdapter.exec(tx, jedis); 62 | return ret.size() == 2 && (Long) ret.get(0) > 0 && (Long) ret.get(1) > 0; 63 | } 64 | 65 | public List getFollowers(int entityType, int entityId, int count) { 66 | String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityId); 67 | return getIdsFromSet(jedisAdapter.zrevrange(followerKey, 0, count)); 68 | } 69 | 70 | public List getFollowers(int entityType, int entityId, int offset, int count) { 71 | String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityId); 72 | return getIdsFromSet(jedisAdapter.zrevrange(followerKey, offset, offset+count)); 73 | } 74 | 75 | public List getFollowees(int userId, int entityType, int count) { 76 | String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType); 77 | return getIdsFromSet(jedisAdapter.zrevrange(followeeKey, 0, count)); 78 | } 79 | 80 | public List getFollowees(int userId, int entityType, int offset, int count) { 81 | String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType); 82 | return getIdsFromSet(jedisAdapter.zrevrange(followeeKey, offset, offset+count)); 83 | } 84 | 85 | public long getFollowerCount(int entityType, int entityId) { 86 | String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityId); 87 | return jedisAdapter.zcard(followerKey); 88 | } 89 | 90 | public long getFolloweeCount(int userId, int entityType) { 91 | String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType); 92 | return jedisAdapter.zcard(followeeKey); 93 | } 94 | 95 | private List getIdsFromSet(Set idset) { 96 | List ids = new ArrayList<>(); 97 | for (String str : idset) { 98 | ids.add(Integer.parseInt(str)); 99 | } 100 | return ids; 101 | } 102 | 103 | /** 104 | * 判断用户是否关注了某个实体 105 | * @param userId 106 | * @param entityType 107 | * @param entityId 108 | * @return 109 | */ 110 | public boolean isFollower(int userId, int entityType, int entityId) { 111 | String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityId); 112 | return jedisAdapter.zscore(followerKey, String.valueOf(userId)) != null; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/controller/MessageController.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.controller; 2 | 3 | import cn.fciasth.zhihu.bean.HostHolder; 4 | import cn.fciasth.zhihu.bean.Message; 5 | import cn.fciasth.zhihu.bean.User; 6 | import cn.fciasth.zhihu.service.MessageService; 7 | import cn.fciasth.zhihu.service.UserService; 8 | import cn.fciasth.zhihu.util.CommonUtils; 9 | import cn.fciasth.zhihu.vo.ViewObject; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.stereotype.Controller; 14 | import org.springframework.ui.Model; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | import org.springframework.web.bind.annotation.RequestMethod; 17 | import org.springframework.web.bind.annotation.RequestParam; 18 | import org.springframework.web.bind.annotation.ResponseBody; 19 | 20 | import java.util.ArrayList; 21 | import java.util.Date; 22 | import java.util.List; 23 | 24 | @Controller 25 | public class MessageController { 26 | 27 | private static final Logger logger = LoggerFactory.getLogger(MessageController.class); 28 | 29 | @Autowired 30 | private HostHolder hostHolder; 31 | 32 | @Autowired 33 | private MessageService messageService; 34 | 35 | @Autowired 36 | private UserService userService; 37 | 38 | @RequestMapping(path = {"/msg/list"}, method = {RequestMethod.GET}) 39 | public String conversationDetail(Model model) { 40 | try { 41 | if(hostHolder.getUser() == null){ 42 | return "redirect:/reglogin"; 43 | } 44 | int localUserId = hostHolder.getUser().getId(); 45 | List conversations = new ArrayList(); 46 | List conversationList = messageService.getConversationList(localUserId, 0, 10); 47 | for (Message msg : conversationList) { 48 | ViewObject vo = new ViewObject(); 49 | vo.set("conversation", msg); 50 | int targetId = msg.getFromId() == localUserId ? msg.getToId() : msg.getFromId(); 51 | User user = userService.getUser(targetId); 52 | vo.set("user", user); 53 | vo.set("unread", messageService.getConvesationUnreadCount(localUserId, msg.getConversationId())); 54 | conversations.add(vo); 55 | } 56 | model.addAttribute("conversations", conversations); 57 | } catch (Exception e) { 58 | logger.error("获取站内信列表失败" + e.getMessage()); 59 | } 60 | return "letter"; 61 | } 62 | 63 | @RequestMapping(value = "/msg/detail",method = RequestMethod.GET) 64 | public String addMessage(@RequestParam("conversationId") String conversationId, 65 | Model model){ 66 | try { 67 | if(hostHolder.getUser() == null){ 68 | return "redirect:/reglogin"; 69 | } 70 | List messageList = messageService.getConversationDetail(conversationId,0,10); 71 | List messages = new ArrayList(); 72 | for (Message message:messageList 73 | ) { 74 | ViewObject vo = new ViewObject(); 75 | vo.set("message",message); 76 | vo.set("user",userService.getUser(message.getFromId())); 77 | messages.add(vo); 78 | } 79 | model.addAttribute("messages",messages); 80 | }catch (Exception e){ 81 | logger.error("获取详情消息失败"+e.getMessage()); 82 | } 83 | return "letterDetail"; 84 | } 85 | 86 | 87 | @RequestMapping(value = "/msg/addMessage",method = RequestMethod.POST) 88 | @ResponseBody 89 | public String addMessage(@RequestParam("toName") String toName, 90 | @RequestParam("content") String content) { 91 | try { 92 | if(hostHolder.getUser() == null){ 93 | return CommonUtils.getJSONString(999,"用户未登录"); 94 | } 95 | 96 | User user = userService.getUserByName(toName); 97 | if (user == null){ 98 | return CommonUtils.getJSONString(1,"用户不存在"); 99 | } 100 | 101 | Message message = new Message(); 102 | message.setContent(content); 103 | message.setFromId(hostHolder.getUser().getId()); 104 | message.setCreatedDate(new Date()); 105 | message.setToId(user.getId()); 106 | messageService.addMessage(message); 107 | return CommonUtils.getJSONString(0); 108 | 109 | }catch (Exception e){ 110 | logger.error("发送消息失败"+e.getMessage()); 111 | return CommonUtils.getJSONString(1,"发送消息失败"); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/resources/static/scripts/main/component/popupAdd.js: -------------------------------------------------------------------------------- 1 | /** 2 | var oPopupAdd = new PopupAdd({ 3 | data: 初始数据 4 | ok: Function, 发布成功后的回调 5 | }); 6 | */ 7 | (function (window) { 8 | var PopupAdd = Base.createClass('main.component.PopupAdd'); 9 | var Popup = Base.getClass('main.component.Popup'); 10 | var Component = Base.getClass('main.component.Component'); 11 | var Util = Base.getClass('main.base.Util'); 12 | 13 | Base.mix(PopupAdd, Component, { 14 | _tpl: [ 15 | '
', 16 | '
', 17 | '
', 18 | '', 19 | '
', 20 | '
', 21 | '
', 22 | '
问题说明(可选):
', 23 | '
', 24 | '
', 25 | '
', 26 | '
', 27 | '', 28 | '
', 29 | '
', 30 | '
', 31 | '
', 32 | '
', 33 | '
'].join(''), 34 | listeners: [{ 35 | name: 'render', 36 | type: 'custom', 37 | handler: function () { 38 | var that = this; 39 | var oConf = that.rawConfig; 40 | var oEl = that.getEl(); 41 | that.titleIpt = oEl.find('.js-title'); 42 | that.contentIpt = oEl.find('.js-content'); 43 | // 还原值 44 | oConf.data && that.val(oConf.data); 45 | } 46 | }], 47 | show: fStaticShow 48 | }, { 49 | initialize: fInitialize, 50 | val: fVal 51 | }); 52 | 53 | function fStaticShow(oConf) { 54 | var that = this; 55 | var oAdd = new PopupAdd(oConf); 56 | var bSubmit = false; 57 | var oPopup = new Popup({ 58 | width: 540, 59 | title: '提问', 60 | okTxt: '发布', 61 | content: oAdd.html(), 62 | ok: function () { 63 | var that = this; 64 | var oData = oAdd.val(); 65 | if (!oData.title) { 66 | that.error('请填写标题'); 67 | return true; 68 | } 69 | // 避免重复提交 70 | if (bSubmit) { 71 | return true; 72 | } 73 | bSubmit = true; 74 | // 提交内容 75 | $.ajax({ 76 | url: '/question/add', 77 | type: 'post', 78 | data: oData, 79 | dataType: 'json' 80 | }).done(function (oResult) { 81 | // 未登陆,跳转到登陆页面 82 | if (oResult.code === 999) { 83 | window.location.href = '/reglogin?next=' + window.encodeURIComponent(window.location.href); 84 | } else { 85 | oConf.ok && oConf.ok.call(that); 86 | oAdd.emit('ok'); 87 | } 88 | }).fail(function () { 89 | alert('出现错误,请重试'); 90 | }).always(function () { 91 | bSubmit = false; 92 | }); 93 | // 先不关闭 94 | return true; 95 | }, 96 | listeners: { 97 | destroy: function () { 98 | oAdd.destroy(); 99 | } 100 | } 101 | }); 102 | oAdd._popup = oPopup; 103 | Component.setEvents(); 104 | } 105 | 106 | function fInitialize(oConf) { 107 | var that = this; 108 | delete oConf.renderTo; 109 | PopupAdd.superClass.initialize.apply(that, arguments); 110 | } 111 | 112 | function fVal(oData) { 113 | var that = this; 114 | if (arguments.length === 0) { 115 | return { 116 | title: $.trim(that.titleIpt.val()), 117 | content: $.trim(that.contentIpt.val()) 118 | }; 119 | } else { 120 | oData = oData || {}; 121 | that.titleIpt.val($.tirm(oData.title)); 122 | that.contentIpt.val($.trim(oData.content)); 123 | } 124 | } 125 | 126 | })(window); -------------------------------------------------------------------------------- /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 | followUser: fFollowUser, 7 | unFollowUser: fUnFollowUser, 8 | followQuestion: fFollowQuestion, 9 | unFollowQuestion: fUnFollowQuestion, 10 | post: fPost 11 | }); 12 | 13 | /** 14 | * 喜欢 15 | * @param {Object} oConf 16 | * @param {String} oConf.commentId 对象id 17 | * @param {Function} oConf.call 成功回调 18 | * @param {Function} oConf.error 失败回调 19 | * @param {Function} oConf.always 操作的回调 20 | */ 21 | function fLike(oConf) { 22 | var that = this; 23 | that.post({ 24 | url: '/like', 25 | data: {commentId: oConf.commentId}, 26 | call: oConf.call, 27 | error: oConf.error, 28 | always: oConf.always 29 | }); 30 | } 31 | 32 | /** 33 | * 不喜欢 34 | * @param {Object} oConf 35 | * @param {String} oConf.commentId 对象id 36 | * @param {Function} oConf.call 成功回调 37 | * @param {Function} oConf.error 失败回调 38 | * @param {Function} oConf.always 操作的回调 39 | */ 40 | function fDislike(oConf) { 41 | var that = this; 42 | that.post({ 43 | url: '/dislike', 44 | data: {commentId: oConf.commentId}, 45 | call: oConf.call, 46 | error: oConf.error, 47 | always: oConf.always 48 | }); 49 | } 50 | 51 | /** 52 | * 关注用户 53 | * @param {Object} oConf 54 | * @param {String} oConf.userId 用户id 55 | * @param {Function} oConf.call 成功回调 56 | * @param {Function} oConf.error 失败回调 57 | * @param {Function} oConf.always 操作的回调 58 | */ 59 | function fFollowUser(oConf) { 60 | var that = this; 61 | that.post({ 62 | url: '/followUser', 63 | data: {userId: oConf.userId}, 64 | call: oConf.call, 65 | error: oConf.error, 66 | always: oConf.always 67 | }); 68 | } 69 | 70 | /** 71 | * 取消关注用户 72 | * @param {Object} oConf 73 | * @param {String} oConf.userId 用户id 74 | * @param {Function} oConf.call 成功回调 75 | * @param {Function} oConf.error 失败回调 76 | * @param {Function} oConf.always 操作的回调 77 | */ 78 | function fUnFollowUser(oConf) { 79 | var that = this; 80 | that.post({ 81 | url: '/unfollowUser', 82 | data: {userId: oConf.userId}, 83 | call: oConf.call, 84 | error: oConf.error, 85 | always: oConf.always 86 | }); 87 | } 88 | 89 | /** 90 | * 关注问题 91 | * @param {Object} oConf 92 | * @param {String} oConf.questionId 问题id 93 | * @param {Function} oConf.call 成功回调 94 | * @param {Function} oConf.error 失败回调 95 | * @param {Function} oConf.always 操作的回调 96 | */ 97 | function fFollowQuestion(oConf) { 98 | var that = this; 99 | that.post({ 100 | url: '/followQuestion', 101 | data: {questionId: oConf.questionId}, 102 | call: oConf.call, 103 | error: oConf.error, 104 | always: oConf.always 105 | }); 106 | } 107 | 108 | /** 109 | * 取消关注问题 110 | * @param {Object} oConf 111 | * @param {String} oConf.questionId 问题id 112 | * @param {Function} oConf.call 成功回调 113 | * @param {Function} oConf.error 失败回调 114 | * @param {Function} oConf.always 操作的回调 115 | */ 116 | function fUnFollowQuestion(oConf) { 117 | var that = this; 118 | that.post({ 119 | url: '/unfollowQuestion', 120 | data: {questionId: oConf.questionId}, 121 | call: oConf.call, 122 | error: oConf.error, 123 | always: oConf.always 124 | }); 125 | } 126 | 127 | /** 128 | * 简单的 ajax 请求封装 129 | * @param {Object} oConf 130 | * @param {String} oConf.method 请求类型 131 | * @param {String} oConf.url 请求连接 132 | * @param {Object} oConf.data 发送参数 133 | * @param {Function} oConf.call 成功回调 134 | * @param {Function} oConf.error 失败回调 135 | * @param {Function} oConf.always 操作的回调 136 | */ 137 | function fPost(oConf) { 138 | var that = this; 139 | $.ajax({ 140 | method: oConf.method || 'POST', 141 | url: oConf.url, 142 | dataType: 'json', 143 | data: oConf.data 144 | }).done(function (oResult) { 145 | var nCode = oResult.code; 146 | if (oResult.code === 999) { 147 | // 未登录 148 | alert('未登录'); 149 | window.location.href = '/reglogin?next=' + window.encodeURI(window.location.href); 150 | return; 151 | } 152 | nCode === 0 && oConf.call && oConf.call(oResult); 153 | nCode !== 0 && oConf.error && oConf.error(oResult); 154 | }).fail(oConf.error).always(oConf.always); 155 | } 156 | 157 | })(window); -------------------------------------------------------------------------------- /src/main/resources/static/scripts/main/component/popupMsg.js: -------------------------------------------------------------------------------- 1 | /** 2 | var oPopupAdd = new PopupAdd({ 3 | data: 初始数据 4 | toName: String, 姓名 5 | content: String, 内容 6 | }); 7 | */ 8 | (function (window) { 9 | var PopupMsg = Base.createClass('main.component.PopupMsg'); 10 | var Popup = Base.getClass('main.component.Popup'); 11 | var Component = Base.getClass('main.component.Component'); 12 | var Util = Base.getClass('main.base.Util'); 13 | 14 | Base.mix(PopupMsg, Component, { 15 | _tpl: [ 16 | '
', 17 | '
', 18 | '
发给:
', 19 | '
', 20 | '', 21 | '
', 22 | '
', 23 | '
', 24 | '
内容:
', 25 | '
', 26 | '
', 27 | '
', 28 | '
', 29 | '', 30 | '
', 31 | '
', 32 | '
', 33 | '
', 34 | '
', 35 | '
'].join(''), 36 | listeners: [{ 37 | name: 'render', 38 | type: 'custom', 39 | handler: function () { 40 | var that = this; 41 | var oConf = that.rawConfig; 42 | var oEl = that.getEl(); 43 | that.nameIpt = oEl.find('.js-name'); 44 | that.contentIpt = oEl.find('.js-content'); 45 | // 还原值 46 | oConf.data && that.val(oConf.data); 47 | } 48 | }], 49 | show: fStaticShow 50 | }, { 51 | initialize: fInitialize, 52 | val: fVal 53 | }); 54 | 55 | function fStaticShow(oConf) { 56 | var that = this; 57 | var oAdd = new PopupMsg(oConf); 58 | var bSubmit = false; 59 | var oPopup = new Popup({ 60 | width: 540, 61 | title: '发送私信', 62 | okTxt: '发送', 63 | content: oAdd.html(), 64 | ok: function () { 65 | var that = this; 66 | var oData = oAdd.val(); 67 | if (!oData.toName) { 68 | that.error('请填写姓名'); 69 | return true; 70 | } 71 | if (!oData.content) { 72 | that.error('请填写私信内容'); 73 | return true; 74 | } 75 | // 避免重复提交 76 | if (bSubmit) { 77 | return true; 78 | } 79 | bSubmit = true; 80 | // 提交内容 81 | $.ajax({ 82 | url: '/msg/addMessage', 83 | type: 'post', 84 | data: oData, 85 | dataType: 'json' 86 | }).done(function (oResult) { 87 | // 未登陆,跳转到登陆页面 88 | if (oResult.code === 999) { 89 | window.location.href = '/reglogin?next=' + window.encodeURIComponent(window.location.href); 90 | } else if (oResult.code !== 0) { 91 | that.error(oResult.msg || '出现错误,请重试'); 92 | } else { 93 | oConf.ok && oConf.ok.call(that); 94 | oAdd.emit('ok'); 95 | } 96 | }).fail(function () { 97 | alert('出现错误,请重试'); 98 | }).always(function () { 99 | bSubmit = false; 100 | }); 101 | // 先不关闭 102 | return true; 103 | }, 104 | listeners: { 105 | destroy: function () { 106 | oAdd.destroy(); 107 | } 108 | } 109 | }); 110 | oAdd._popup = oPopup; 111 | Component.setEvents(); 112 | } 113 | 114 | function fInitialize(oConf) { 115 | var that = this; 116 | delete oConf.renderTo; 117 | PopupMsg.superClass.initialize.apply(that, arguments); 118 | } 119 | 120 | function fVal(oData) { 121 | var that = this; 122 | if (arguments.length === 0) { 123 | return { 124 | toName: $.trim(that.nameIpt.val()), 125 | content: $.trim(that.contentIpt.val()) 126 | }; 127 | } else { 128 | oData = oData || {}; 129 | that.nameIpt.val($.tirm(oData.toName)); 130 | that.contentIpt.val($.trim(oData.content)); 131 | } 132 | } 133 | 134 | })(window); -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/service/SensitiveService.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.service; 2 | 3 | import org.apache.commons.lang.CharUtils; 4 | import org.apache.commons.lang.StringUtils; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.InitializingBean; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.io.BufferedReader; 11 | import java.io.InputStream; 12 | import java.io.InputStreamReader; 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | 16 | @Service 17 | public class SensitiveService implements InitializingBean { 18 | 19 | private static final Logger logger = LoggerFactory.getLogger(SensitiveService.class); 20 | 21 | /** 22 | * 默认敏感词替换符 23 | */ 24 | private static final String DEFAULT_REPLACEMENT = "***"; 25 | 26 | 27 | private class TrieNode { 28 | 29 | /** 30 | * true 关键词的终结 ; false 继续 31 | */ 32 | private boolean end = false; 33 | 34 | /** 35 | * key下一个字符,value是对应的节点 36 | */ 37 | private Map subNodes = new HashMap<>(); 38 | 39 | /** 40 | * 向指定位置添加节点树 41 | */ 42 | void addSubNode(Character key, TrieNode node) { 43 | subNodes.put(key, node); 44 | } 45 | 46 | /** 47 | * 获取下个节点 48 | */ 49 | TrieNode getSubNode(Character key) { 50 | return subNodes.get(key); 51 | } 52 | 53 | boolean isKeywordEnd() { 54 | return end; 55 | } 56 | 57 | void setKeywordEnd(boolean end) { 58 | this.end = end; 59 | } 60 | 61 | public int getSubNodeCount() { 62 | return subNodes.size(); 63 | } 64 | 65 | 66 | } 67 | 68 | 69 | /** 70 | * 根节点 71 | */ 72 | private TrieNode rootNode = new TrieNode(); 73 | 74 | 75 | /** 76 | * 判断是否是一个符号 77 | */ 78 | private boolean isSymbol(char c) { 79 | int ic = (int) c; 80 | // 0x2E80-0x9FFF 东亚文字范围 81 | return !CharUtils.isAsciiAlphanumeric(c) && (ic < 0x2E80 || ic > 0x9FFF); 82 | } 83 | 84 | 85 | /** 86 | * 过滤敏感词 87 | */ 88 | public String filter(String text) { 89 | if (StringUtils.isBlank(text)) { 90 | return text; 91 | } 92 | String replacement = DEFAULT_REPLACEMENT; 93 | StringBuilder result = new StringBuilder(); 94 | 95 | TrieNode tempNode = rootNode; 96 | int begin = 0; // 回滚数 97 | int position = 0; // 当前比较的位置 98 | 99 | while (position < text.length()) { 100 | char c = text.charAt(position); 101 | // 空格直接跳过 102 | if (isSymbol(c)) { 103 | if (tempNode == rootNode) { 104 | result.append(c); 105 | ++begin; 106 | } 107 | ++position; 108 | continue; 109 | } 110 | 111 | tempNode = tempNode.getSubNode(c); 112 | 113 | // 当前位置的匹配结束 114 | if (tempNode == null) { 115 | // 以begin开始的字符串不存在敏感词 116 | result.append(text.charAt(begin)); 117 | // 跳到下一个字符开始测试 118 | position = begin + 1; 119 | begin = position; 120 | // 回到树初始节点 121 | tempNode = rootNode; 122 | } else if (tempNode.isKeywordEnd()) { 123 | // 发现敏感词, 从begin到position的位置用replacement替换掉 124 | result.append(replacement); 125 | position = position + 1; 126 | begin = position; 127 | tempNode = rootNode; 128 | } else { 129 | ++position; 130 | } 131 | } 132 | 133 | result.append(text.substring(begin)); 134 | 135 | return result.toString(); 136 | } 137 | 138 | private void addWord(String lineTxt) { 139 | TrieNode tempNode = rootNode; 140 | // 循环每个字节 141 | for (int i = 0; i < lineTxt.length(); ++i) { 142 | Character c = lineTxt.charAt(i); 143 | // 过滤空格 144 | if (isSymbol(c)) { 145 | continue; 146 | } 147 | TrieNode node = tempNode.getSubNode(c); 148 | 149 | if (node == null) { // 没初始化 150 | node = new TrieNode(); 151 | tempNode.addSubNode(c, node); 152 | } 153 | 154 | tempNode = node; 155 | 156 | if (i == lineTxt.length() - 1) { 157 | // 关键词结束, 设置结束标志 158 | tempNode.setKeywordEnd(true); 159 | } 160 | } 161 | } 162 | 163 | 164 | @Override 165 | public void afterPropertiesSet() throws Exception { 166 | rootNode = new TrieNode(); 167 | 168 | try { 169 | InputStream is = Thread.currentThread().getContextClassLoader() 170 | .getResourceAsStream("SensitiveWords.txt"); 171 | InputStreamReader read = new InputStreamReader(is); 172 | BufferedReader bufferedReader = new BufferedReader(read); 173 | String lineTxt; 174 | while ((lineTxt = bufferedReader.readLine()) != null) { 175 | lineTxt = lineTxt.trim(); 176 | addWord(lineTxt); 177 | } 178 | read.close(); 179 | } catch (Exception e) { 180 | logger.error("读取敏感词文件失败" + e.getMessage()); 181 | } 182 | } 183 | 184 | public static void main(String[] argv) { 185 | SensitiveService s = new SensitiveService(); 186 | s.addWord("色情"); 187 | s.addWord("好色"); 188 | System.out.print(s.filter("你好X色**情XX")); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /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 | destroy: fDestroy, 17 | getData: fGetData, 18 | // 禁止滚动 19 | forbidScroll: fForbidScroll, 20 | // 重写emit 21 | emit: fEmit, 22 | // 内部方法 23 | _setCustomEvent: _fSetCustomEvent, 24 | _setDomEvent: _fSetDomEvent 25 | }); 26 | 27 | function fStaticSetEvents() { 28 | var that = this; 29 | var aQueue = Component._domQueue; 30 | var oQueue; 31 | while (aQueue.length) { 32 | oQueue = aQueue.shift(); 33 | oQueue._setDomEvent(); 34 | oQueue.emit('render'); 35 | } 36 | } 37 | 38 | function fInitialize(oConf) { 39 | var that = this; 40 | that.rawConfig = oConf; 41 | that.domId = 'jsCpn' + (Component._cIndex++); 42 | that._setCustomEvent(); 43 | Component._domQueue.push(that); 44 | oConf.renderTo && that.render(); 45 | } 46 | 47 | function fRender() { 48 | var that = this; 49 | var oConf = that.rawConfig; 50 | var oRenderTo = $(oConf.renderTo); 51 | var sRenderBy = oConf.renderBy || 'append'; 52 | var oEl = that.getEl(); 53 | oRenderTo[sRenderBy](oEl); 54 | that._setDomEvent(); 55 | that.emit('render'); 56 | } 57 | 58 | function fGetEl() { 59 | var that = this; 60 | if (that.$el) { 61 | return that.$el; 62 | } 63 | var oEl = $('#' + that.domId); 64 | if (oEl.get(0)) { 65 | that.$el = oEl; 66 | return oEl; 67 | } 68 | 69 | var sHtml = that.html(); 70 | that.$el = $(sHtml); 71 | return that.$el; 72 | } 73 | 74 | function fHtml() { 75 | var that = this; 76 | var oConf = that.rawConfig; 77 | var oConstructor = that.constructor; 78 | var sTpl = oConstructor._tpl || Component._tpl; 79 | var oData = that.getData(that.rawConfig); 80 | var sHtml = Base.tpl(sTpl, oData); 81 | // id 和 class 82 | /* jshint ignore:start */ 83 | sHtml = sHtml.replace(/^(\<\w+)([ \>])/, '$1' + ' id="' + that.domId + '"$2'); 84 | /* jshint ignore:end */ 85 | sHtml = sHtml.replace('class="', 'class="' + (oConf.cls || '') + ' '); 86 | return sHtml; 87 | } 88 | 89 | function fDestroy() { 90 | var that = this; 91 | var oEl = that.getEl(); 92 | oEl.remove(); 93 | that.emit('destroy'); 94 | that.unbindAll(); 95 | } 96 | 97 | function fGetData(oConf) { 98 | return oConf; 99 | } 100 | 101 | function fForbidScroll(oEl, bForbid) { 102 | $(oEl).css('overflow', bForbid === false ? 'auto' : 'hidden'); 103 | } 104 | 105 | function fEmit(sName) { 106 | var that = this; 107 | if (sName === 'render') { 108 | if (that.rendered) { 109 | return; 110 | } 111 | that.rendered = true; 112 | } 113 | Event.emit.apply(that, arguments); 114 | } 115 | 116 | function _fSetCustomEvent() { 117 | var that = this; 118 | if (that._setedCustomEvent) { 119 | return; 120 | } 121 | that._setedCustomEvent = true; 122 | var oConf = that.rawConfig; 123 | var oConstructor = that.constructor; 124 | $.each(oConstructor.listeners, function (_, oEvent) { 125 | oEvent.type === 'custom' && oEvent.name && oEvent.handler && that.on(oEvent.name, oEvent.handler); 126 | }); 127 | $.each(oConf.listeners, function (sName, fCb) { 128 | Base.isFunction(fCb) && that.on(sName, fCb); 129 | }); 130 | } 131 | 132 | function _fSetDomEvent() { 133 | var that = this; 134 | if (that._setedDomEvent) { 135 | return; 136 | } 137 | that._setedDomEvent = true; 138 | var oConf = that.rawConfig; 139 | var oEl = that.getEl(); 140 | var oConstructor = that.constructor; 141 | // 构造器上的事件 142 | $.each(oConstructor.listeners, function (_, oEvent) { 143 | oEvent.type !== 'custom' && _fBind(oEvent.name, oEvent); 144 | }); 145 | // 配置上面的事件 146 | $.each(oConf.listeners, function (sName, oEvent) { 147 | Base.isObject(oEvent) && _fBind(sName, oEvent); 148 | }); 149 | // 删除dom事件队列 150 | for (var i = Component._domQueue.length - 1; i >= 0; i--) { 151 | if (Component._domQueue[i] === that) { 152 | Component._domQueue.splice(i, 1); 153 | } 154 | } 155 | function _fBind(sName, oEvent) { 156 | var aMatch = sName.match(/^(\S+)\s*(.*)$/); 157 | var sEvent = $.trim(aMatch[1]); 158 | var sSelector = $.trim(aMatch[2]); 159 | var fHandler = oEvent.handler; 160 | if (Base.isFunction(fHandler)) { 161 | if (sSelector) { 162 | oEvent.type === 'bind' && oEl.find(sSelector).on(sEvent, Base.bind(fHandler, that)); 163 | oEvent.type !== 'bind' && oEl.on(sEvent, sSelector, Base.bind(fHandler, that)); 164 | } else { 165 | oEl.on(sEvent, Base.bind(fHandler, that)); 166 | } 167 | } 168 | } 169 | } 170 | 171 | })(window); -------------------------------------------------------------------------------- /src/main/resources/static/scripts/instant.e7a17de6.js: -------------------------------------------------------------------------------- 1 | !function(){var a=document.cookie.match(/actioncontinuationhash=([^;]+)/),b=a&&a[1];b&&(location.hash=decodeURIComponent(b))}(this),function(a,b){function c(a){function b(a,b){var c;return window.getComputedStyle?c=window.getComputedStyle(a,null).getPropertyValue(b):a.currentStyle&&(c=a.currentStyle[b]),c}for(var c=a.parentNode;"inline"===b(c,"display");)c=c.parentNode;return c}function d(a){var b=+a.getAttribute("data-rawwidth"),d=+a.getAttribute("data-rawheight");if(b&&d&&!a.style.width&&!a.style.height){var e=c(a).clientWidth,f=b,g=d;b>e&&(f=e,g=f*(d/b)),a.style.width=f+"px",a.style.height=g+"px"}}function e(){var a=b.querySelectorAll;if(a)return a.call(b,"img.lazy");var c=[];return f(document.images,function(a){/\blazy\b/.test(a.className)&&c.push(a)}),c}function f(a,b){for(var c=a.length-1;c>=0;c--)b(a[c])}a.stretchLazyImageHolders=function(){f(e(),d)}}(this,document),function(a,b){a.instant={apply:function(){for(var a;a=b.shift();)a()}}}(this,[function(){window.stretchLazyImageHolders()}]),function(){var a=window.document.documentElement,b=window.localStorage;if(b){var c=/^\/(signup|signin)?$/.test(location.pathname);if((c||"true"!==b.getItem("hideAppPromotionBar"))&&(a.className+=" is-AppPromotionBarVisible"),"#showWechatShareTip"===location.hash&&"true"!==b.getItem("hideWechatShareTip"))try{a.className+=" show-wechat-share-tip",b.setItem("hideWechatShareTip","true")}catch(d){}}}(this),function(a,b,c){function d(a,b){return typeof a===b}function e(){var a,b,c,e,f,g,h;for(var i in s)if(s.hasOwnProperty(i)){if(a=[],b=s[i],b.name&&(a.push(b.name.toLowerCase()),b.options&&b.options.aliases&&b.options.aliases.length))for(c=0;cn;n++)if(p=a[n],q=D.style[p],g(p,"-")&&(p=m(p)),D.style[p]!==c){if(f||d(e,"undefined"))return i(),"pfx"==b?p:!0;try{D.style[p]=e}catch(s){}if(D.style[p]!=q)return i(),"pfx"==b?p:!0}return i(),!1}function o(a,b){return function(){return a.apply(b,arguments)}}function p(a,b,c){var e;for(var f in a)if(a[f]in b)return c===!1?a[f]:(e=b[a[f]],d(e,"function")?o(e,c||b):e);return!1}function q(a,b,c,e,f){var g=a.charAt(0).toUpperCase()+a.slice(1),h=(a+" "+B.join(g+" ")+g).split(" ");return d(b,"string")||d(b,"undefined")?n(h,b,e,f):(h=(a+" "+z.join(g+" ")+g).split(" "),p(h,b,c))}function r(a,b,d){return q(a,c,c,b,d)}var s=[],t={_version:"3.3.0",_config:{classPrefix:"",enableClasses:!0,enableJSClass:!0,usePrefixes:!0},_q:[],on:function(a,b){var c=this;setTimeout(function(){b(c[a])},0)},addTest:function(a,b,c){s.push({name:a,fn:b,options:c})},addAsyncTest:function(a){s.push({name:null,fn:a})}},u=function(){};u.prototype=t,u=new u;var v=[],w=b.documentElement,x="svg"===w.nodeName.toLowerCase(),y="Moz O ms Webkit",z=t._config.usePrefixes?y.toLowerCase().split(" "):[];t._domPrefixes=z;var A=t._config.usePrefixes?" -webkit- -moz- -o- -ms- ".split(" "):[];t._prefixes=A;var B=t._config.usePrefixes?y.split(" "):[];t._cssomPrefixes=B;var C={elem:h("modernizr")};u._q.push(function(){delete C.elem});var D={style:C.elem.style};u._q.unshift(function(){delete D.style}),t.testAllProps=q;var E=function(b){var d,e=A.length,f=a.CSSRule;if("undefined"==typeof f)return c;if(!b)return!1;if(b=b.replace(/^@/,""),d=b.replace(/-/g,"_").toUpperCase()+"_RULE",d in f)return"@"+b;for(var g=0;e>g;g++){var h=A[g],i=h.toUpperCase()+"_"+d;if(i in f)return"@-"+h.toLowerCase()+"-"+b}return!1};t.atRule=E;t.prefixed=function(a,b,c){return 0===a.indexOf("@")?E(a):(-1!=a.indexOf("-")&&(a=m(a)),b?q(a,b,c):q(a,"pfx"))};t.testAllProps=r;var F=(t.testProp=function(a,b,d){return n([a],c,b,d)},t.testStyles=j);u.addTest("cssanimations",r("animationName","a",!0)),u.addTest("csstransforms",function(){return-1===navigator.userAgent.indexOf("Android 2.")&&r("transform","scale(1)",!0)}),u.addTest("csstransitions",r("transition","all",!0)),u.addTest("flexbox",r("flexBasis","1px",!0)),u.addTest("touchevents",function(){var c;if("ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch)c=!0;else{var d=["@media (",A.join("touch-enabled),("),"heartz",")","{#modernizr{top:9px;position:absolute}}"].join("");F(d,function(a){c=9===a.offsetTop})}return c}),e(),f(v),delete t.addTest,delete t.addAsyncTest;for(var G=0;G', 22 | '', 26 | '', 36 | ''].join(''), 37 | listeners: [{ 38 | name: 'render', 39 | type: 'custom', 40 | handler: function () { 41 | var that = this; 42 | var oConf = that.rawConfig; 43 | var oEl = that.getEl(); 44 | // 常用元素 45 | that.titleEl = oEl.find('span.js-title'); 46 | that.contentEl = oEl.find('div.js-content'); 47 | that.tipsEl = oEl.find('span.js-error'); 48 | // 调整大小 49 | oEl.outerWidth(oConf.width || 520); 50 | oConf.height && that.contentEl.outerHeight(oConf.height); 51 | // 禁止body滚动 52 | that.forbidScroll(document.body); 53 | // 创建遮罩层 54 | that.initMask(); 55 | // 调整z-index 56 | oEl.css('zIndex', Popup.zIndex++); 57 | // 去掉头部 58 | oConf.hasNoHeader && oEl.find('div.js-head').remove(); 59 | // 去掉底部 60 | oConf.hasNoFooter && oEl.find('div.js-footer').remove(); 61 | // 位置居中 62 | that.fixPosition(); 63 | // 绑定窗口变化事件 64 | that.resizeCb = Base.bind(that.fixPosition, that); 65 | $(window).resize(that.resizeCb); 66 | } 67 | }, { 68 | name: 'destroy', 69 | type: 'custom', 70 | handler: function () { 71 | var that = this; 72 | // 启动滚动 73 | !that.isForbidScroll && that.forbidScroll(document.body, false); 74 | // 移除遮罩层 75 | that.maskEl && that.maskEl.remove(); 76 | // 取消窗口变化事件 77 | $(window).unbind('resize', that.resizeCb); 78 | } 79 | }, { 80 | name: 'click .js-close', 81 | handler: function () { 82 | var that = this; 83 | that.close(); 84 | } 85 | }, { 86 | name: 'click .js-cancel', 87 | handler: function () { 88 | var that = this; 89 | var oConf = that.rawConfig; 90 | oConf.cancel && oConf.cancel.call(that); 91 | that.close(true); 92 | } 93 | }, { 94 | name: 'click .js-ok', 95 | handler: function () { 96 | var that = this; 97 | var oConf = that.rawConfig; 98 | // 禁止返回 99 | if (oConf.ok && oConf.ok.call(that) === true) { 100 | return; 101 | } 102 | that.close(true); 103 | } 104 | }] 105 | }, { 106 | initialize: fInitialize, 107 | initMask: fInitMask, 108 | fixPosition: fFixPosition, 109 | close: fClose, 110 | error: fError, 111 | getData: fGetData 112 | }); 113 | 114 | function fInitialize(oConf) { 115 | var that = this; 116 | var oBody = $(document.body); 117 | oConf.renderTo = oBody; 118 | that.isForbidScroll = oBody.css('overflow-y') === 'hidden'; 119 | Popup.superClass.initialize.apply(that, arguments); 120 | } 121 | 122 | function fInitMask() { 123 | var that = this; 124 | var oConf = that.rawConfig; 125 | if (!that.maskEl) { 126 | that.maskEl = $('
'); 127 | oConf.renderTo.append(that.maskEl); 128 | } 129 | } 130 | 131 | function fFixPosition() { 132 | var that = this; 133 | var oEl = that.getEl(); 134 | var oWin = $(window); 135 | var oDoc = $(document); 136 | var nElWidth = oEl.width(); 137 | var nElHeight = oEl.height(); 138 | var nWinWidth = oWin.width(); 139 | var nWinHeight = oWin.height(); 140 | var nScrollTop = Math.max(oWin.scrollTop() || oDoc.scrollTop()); 141 | // 调整元素大小 142 | oEl.css({ 143 | left: nWinWidth > nElWidth ? (nWinWidth - nElWidth) / 2 : 0, 144 | top: (nWinHeight > nElHeight ? (nWinHeight - nElHeight) / 2 : 0) + nScrollTop 145 | }); 146 | // 调整遮罩层大小 147 | that.maskEl.css({ 148 | width: '100%', 149 | height: nWinHeight, 150 | top: nScrollTop 151 | }); 152 | } 153 | 154 | function fClose(bNoEmit) { 155 | var that = this; 156 | !bNoEmit && that.emit('close'); 157 | that.destroy(); 158 | } 159 | 160 | function fError(sContent) { 161 | var that = this; 162 | sContent = $.trim(sContent); 163 | that.tipsEl.html(sContent); 164 | that.tipsEl[sContent ? 'show' : 'hide'](); 165 | } 166 | 167 | function fGetData(oConf) { 168 | var that = this; 169 | return { 170 | title: oConf.title || '提示', 171 | content: oConf.content, 172 | cancelTxt: oConf.cancelTxt || '取消', 173 | okTxt: oConf.okTxt || '确定' 174 | }; 175 | } 176 | 177 | })(window); -------------------------------------------------------------------------------- /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/page-index.d7b54ac7.js: -------------------------------------------------------------------------------- 1 | (function(z){ 2 | var bS=function(){z.R.call(this)};var cS=function(){(0,window.$)("body").on("click",".org-tip a",function(a){a.preventDefault();z.X.alert({title:"申请注册机构帐号",modal:!0,content:(0,window.$)("#org-tip").html(),buttons:{kC:"确认"}})})}; 3 | var dS=function(){var a=(0,window.$)(".QRCode-card"),b=(0,window.$)(".QRCode-toggleButton"),c=(0,window.$)(".QRCode-toggleButtonText",b);(0,window.$)("body").on("click",function(d){a.is(":visible")?Boolean((0,window.$)(d.target).closest(a).length)||(a.addClass("fadeOutDown").onTransitionEnd(function(){a.hide().removeClass("fadeOutDown")},200),c.text("下载知乎 App")):Boolean((0,window.$)(d.target).closest(b).length)&&(a.addClass("fadeInUp").show().onTransitionEnd(function(){a.removeClass("fadeInUp")}, 4 | 200),c.text("关闭二维码"))})};var eS=function(){var a=(0,window.$)(".sign-flow");if(a.length){var b=new fS;b.w(a[0]);b.g("trackRequested",function(a){a.preventDefault();a.label="home_default";z.xi(a)})}}; 5 | var gS=function(){function a(){var a=window.particlesJS;a&&((0,window.$)("\x3cdiv\x3e",{id:"particles"}).appendTo("body"),a("particles",c))}var b=!!window.HTMLCanvasElement,c={particles:{number:{value:20,density:{enable:!0,value_area:1E3}},color:{value:"#e1e1e1"},shape:{type:"circle",stroke:{width:0,color:"#000000"},polygon:{nb_sides:5},image:{src:"img/github.svg",width:100,height:100}},opacity:{value:.5,random:!1,anim:{enable:!1,speed:1,opacity_min:.1,sync:!1}},size:{value:15,random:!0,anim:{enable:!1, 6 | speed:180,size_min:.1,sync:!1}},line_linked:{enable:!0,distance:650,color:"#cfcfcf",opacity:.26,width:1},move:{enable:!0,speed:2,direction:"none",random:!0,straight:!1,out_mode:"out",bounce:!1,attract:{enable:!1,rotateX:600,rotateY:1200}}},interactivity:{detect_on:"canvas",events:{onhover:{enable:!1,mode:"repulse"},onclick:{enable:!1,mode:"push"},resize:!0},modes:{grab:{distance:400,line_linked:{opacity:1}},bubble:{distance:400,size:40,duration:2,opacity:8,speed:3},repulse:{distance:200,duration:.4}, 7 | push:{particles_nb:4},remove:{particles_nb:2}}},retina_detect:!0};(0,window.$)(function(){b&&window.$.ajax({url:"/static/revved/components/particles.js/particles.min.00debcf6.js",dataType:"script",cache:!0}).then(a)})};var fS=function(){z.R.call(this)};var hS=function(a){return!/^\+?[0-9]+$/.test(a)};var iS=function(a,b){a.account&&(a[b]=a.account,delete a.account)}; 8 | var jS=function(a){var b=(0,window.$)(".index-tab-navs a",a.m()),c=(0,z.q)(function(){var a=window.location.hash||b.filter(".active").attr("href"),c=b.filter(function(){return(0,window.$)(this).attr("href")===a});c.addClass("active").siblings().removeClass("active");c.parent().attr("data-active-index",c.index());"#signin"===a?this.Mk.is(":visible")||(this.Kf.hide(),this.Mk.show(),this.sl()):"#signup"!==a||this.Kf.is(":visible")||(this.Mk.hide(),this.Kf.show(),this.sl())},a);a.v().g(window,"hashchange", 9 | c);c()};var kS=function(a){a.Mk=(0,window.$)(".view-signin",a.m());var b=(0,window.$)("form",a.Mk),c=(0,window.$)("button.submit",b);b.validate(window.$.extend({},z.Xm,{submitHandler:z.uk(c,window.$.proxy(a.bA,a))}));var d=(0,window.$)('input[name\x3d"account"]',b);(0,window.$)(".unable-login",a.Mk).click(function(){(new z.Fk(d.val())).show()});b=(0,window.$)(".captcha-module, .Captcha",b).get(0);a.rt=new z.dk("login");a.rt.w(b);z.hk(a.rt)}; 10 | var lS=function(a){a.Kf=(0,window.$)(".view-signup",a.m());var b=(0,window.$)("form",a.Kf);a.xh=!(0,window.$)('input[name\x3d"phone_num"]',b).length;var c=(0,window.$)("button.submit",b);b.validate(window.$.extend({},z.Xm,{submitHandler:z.uk(c,window.$.proxy(a.lp,a))}));var d=(0,window.$)('input[name\x3d"account"]',b);a.Kf.on("click",".switch-to-login",function(){window.location.hash="#signin";var b=d.val();b&&(0,window.$)('input[name\x3d"account"]',a.fX).val(b)}).on("change",".agreement input",function(){var a= 11 | (0,window.$)(this);a.is(":checked")&&a.parents(".agreement").find(".error").remove()});c=(0,window.$)(".input-wrapper[data-type]",b).get(0);a.xp=new z.dk;a.xp.w(c);z.hk(a.xp);a.Mm=new z.Sk(b);a.Mm.start()};var mS=function(){var a=z.aj.get("accountcallback");z.aj.remove("accountcallback");if(a){a=(0,window.decodeURIComponent)(a);try{var b=JSON.parse(a);z.Y.dispatchEvent(new z.Zj("accountcallback",b))}catch(c){}}};z.x(bS,z.R);bS.prototype.init=function(){this.w(window.document.body)}; 12 | bS.prototype.C=function(a){bS.o.C.call(this,a);eS();gS();dS();cS();z.xi({category:"view_home",action:"visit_home"})};z.x(fS,z.R);z.e=fS.prototype;z.e.C=function(){fS.o.C.call(this);this.Vy()};z.e.na=function(a){this.dispatchEvent(window.$.extend({type:"trackRequested"},a))}; 13 | z.e.Vy=function(){function a(a,b){(0,window.$)(":submit",a).click(function(){return b(a)});(0,window.$)("keydown",a).click(function(c){if(13===c.keyCode)return b(a)})}kS(this);lS(this);this.so();mS();this.sl();jS(this);var b=this,c=this.m();a((0,window.$)(".view-signin form",c),function(a){a=a.ie().account;var c=hS(a);b.na({category:"sign_in",action:a?"click_sign_in_submit_"+(c?"email":"phone"):"click_sign_in_submit_no_channel"})});a((0,window.$)(".view-signup form",c),function(a){a=a.ie();a=a.phone_num|| 14 | a.account;var c=b.xh?hS(a):!1;b.na({category:"sign_up",action:a?b.xh?"click_sign_up_submit_"+(c?"email":"phone"):"click_sign_up_submit_phone":"click_sign_up_submit_no_channel"})});(0,window.$)(".js-bindweibo",c).click(function(){b.na({category:"sign_in_or_sign_up",action:"click_sign_with_weibo_start"});z.ak("sina")});(0,window.$)(".js-bindqq",c).click(function(){b.na({category:"sign_in_or_sign_up",action:"click_sign_with_qq_start"});z.ak("qq")});(0,window.$)(".js-bindwechat",c).click(function(){b.na({category:"sign_in_or_sign_up", 15 | action:"click_sign_with_wechat_start"});z.ak("wechat")});(0,window.$)(".js-toggle-sns-buttons").click(function(){var a=(0,window.$)(".sns-buttons");a.hasClass("is-visible")?a.onTransitionEnd(function(){a.css("visibility","hidden")},250).removeClass("is-visible"):a.css("visibility","visible").addClass("is-visible")})}; 16 | z.e.bA=function(a){var b=this,c=(0,window.$)(a),d=c.ie(),f=hS(d.account);this.na({category:"sign_in",action:"sign_in_front_end_pass_"+(f?"email":"phone")});a=f?"/login/email":"/login/phone_num";z.Hk&&(d.is_org_page=1);f?iS(d,"email"):iS(d,"phone_num");var g=this.rt.info();return window.$.post(a,d,function(a){var k=a&&!a.r,m=Object.assign({},{status:{result:k?"Success":a.data&&a.data.captcha?"BadCaptcha":"Fail"}},g);k?(b.na({wc:!0,category:"sign_in",action:"sign_in_success_"+(f?"email":"phone")}), 17 | b.Od()):(k=a.errcode,1991831===k?(a=new z.Dk({data:{phone_num:d.phone_num,password:d.password},hT:function(a){c.data("validator").showErrors(a)}}),a.Db(b),a.G(!0)):200001===k?(a='机构帐号\x3cspan\x3e · \x3c/span\x3e\x3ca href\x3d"/org/signin"\x3e点此登录\x3c/a\x3e',z.Hk&&(a='个人帐号\x3cspan\x3e · \x3c/span\x3e\x3ca href\x3d"/signin"\x3e点此登录\x3c/a\x3e'),c.data("validator").showErrors({account:a})):(z.hk(b.rt),c.data("validator").showErrors(a.data)));b.trackEvent(b.Mk.get(0),"SignIn","Button",m)})}; 18 | z.e.lp=function(a){var b=this;a=(0,window.$)(a);var c=a.ie(),d=b.xh?hS(c.account):!1;b.na({wc:!0,category:"sign_up",action:"sign_up_front_end_pass_"+(d?"email":"phone")});var f=d?"/register/email":"/register/phone_num/validation";z.Hk&&(d=!0,f="/register/org");d?iS(c,"email"):iS(c,"phone_num");var g=this.Mm.stop();c.userInfo=JSON.stringify(g);var h=a.data("validator"),k=b.xp.info();return window.$.post(f,c,function(a){var f=a&&!a.r,g=Object.assign({},{status:{result:f?"Success":a.data&&a.data.captcha? 19 | "BadCaptcha":"Fail"}},k);d?f?(b.na({wc:!0,category:"sign_up",action:"sign_up_success_email"}),b.Od()):(z.hk(b.xp),h.showErrors(a.data)):f?(a=new z.hw(c),a.Db(b),a.G(!0)):(z.hk(b.xp),b.xh||iS(a.data,"phone_num"),h.showErrors(a.data));b.trackEvent(b.Kf.get(0),"SignUp","Button",g)})};z.e.sl=function(){if(!z.xk){var a=this.m();(0,window.setTimeout)(function(){(0,window.$)("input[placeholder]:visible",a).placeholder()},10)}};z.e.so=function(){z.ck(this.v(),this.uS)}; 20 | z.e.uS=function(a){a=a.xf;var b={qqconn:"qq",wechat:"wechat",sina:"weibo"}[a.type];1===a.login?(this.na({wc:!0,category:"sign_in",action:"sign_in_success_"+b}),this.Od()):(a=new z.Kk(a),a.Db(this),a.show())};z.e.Od=function(){var a=(new z.ph(window.location.href)).ze().get("next");a?window.location.href=a:window.location.reload()};z.e.trackEvent=function(a,b,c,d){(0,z.Gi)(a,{action:b,element:c},d)};z.v("ZH.entrySignPage",function(){(new bS).init()});z.jg("page-index");}).call(this, __z_z__); 21 | //# sourceURL=/static/revved/-/js/closure/page-index.d7b54ac7.js -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/controller/FollowController.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.controller; 2 | 3 | import cn.fciasth.zhihu.async.EventModel; 4 | import cn.fciasth.zhihu.async.EventProducer; 5 | import cn.fciasth.zhihu.async.EventType; 6 | import cn.fciasth.zhihu.bean.EntityType; 7 | import cn.fciasth.zhihu.bean.HostHolder; 8 | import cn.fciasth.zhihu.bean.Question; 9 | import cn.fciasth.zhihu.bean.User; 10 | import cn.fciasth.zhihu.service.CommentService; 11 | import cn.fciasth.zhihu.service.FollowService; 12 | import cn.fciasth.zhihu.service.QuestionService; 13 | import cn.fciasth.zhihu.service.UserService; 14 | import cn.fciasth.zhihu.util.CommonUtils; 15 | import cn.fciasth.zhihu.vo.ViewObject; 16 | import org.springframework.beans.factory.ObjectFactory; 17 | import org.springframework.beans.factory.annotation.Autowired; 18 | import org.springframework.stereotype.Controller; 19 | import org.springframework.ui.Model; 20 | import org.springframework.web.bind.annotation.*; 21 | import org.springframework.web.servlet.View; 22 | import org.springframework.web.servlet.config.annotation.InterceptorRegistration; 23 | 24 | import java.util.*; 25 | 26 | 27 | @Controller 28 | public class FollowController { 29 | @Autowired 30 | FollowService followService; 31 | 32 | @Autowired 33 | CommentService commentService; 34 | 35 | @Autowired 36 | QuestionService questionService; 37 | 38 | @Autowired 39 | UserService userService; 40 | 41 | @Autowired 42 | HostHolder hostHolder; 43 | 44 | @Autowired 45 | EventProducer eventProducer; 46 | 47 | @RequestMapping(path = {"/followUser"}, method = {RequestMethod.POST, RequestMethod.GET}) 48 | @ResponseBody 49 | public String followUser(@RequestParam("userId") int userId) { 50 | if (hostHolder.getUser() == null) { 51 | return CommonUtils.getJSONString(999); 52 | } 53 | 54 | boolean ret = followService.follow(hostHolder.getUser().getId(), EntityType.ENTITY_USER, userId); 55 | 56 | eventProducer.fireEvent(new EventModel(EventType.FOLLOW) 57 | .setActorId(hostHolder.getUser().getId()).setEntityId(userId) 58 | .setEntityType(EntityType.ENTITY_USER).setEntityOwnerId(userId)); 59 | 60 | // 返回关注的人数 61 | return CommonUtils.getJSONString(ret ? 0 : 1, String.valueOf(followService.getFolloweeCount(hostHolder.getUser().getId(), EntityType.ENTITY_USER))); 62 | } 63 | 64 | @RequestMapping(path = {"/unfollowUser"}, method = {RequestMethod.POST}) 65 | @ResponseBody 66 | public String unfollowUser(@RequestParam("userId") int userId) { 67 | if (hostHolder.getUser() == null) { 68 | return CommonUtils.getJSONString(999); 69 | } 70 | 71 | boolean ret = followService.unfollow(hostHolder.getUser().getId(), EntityType.ENTITY_USER, userId); 72 | 73 | eventProducer.fireEvent(new EventModel(EventType.UNFOLLOW) 74 | .setActorId(hostHolder.getUser().getId()).setEntityId(userId) 75 | .setEntityType(EntityType.ENTITY_USER).setEntityOwnerId(userId)); 76 | 77 | // 返回关注的人数 78 | return CommonUtils.getJSONString(ret ? 0 : 1, String.valueOf(followService.getFolloweeCount(hostHolder.getUser().getId(), EntityType.ENTITY_USER))); 79 | } 80 | 81 | @RequestMapping(path = {"/followQuestion"}, method = {RequestMethod.POST}) 82 | @ResponseBody 83 | public String followQuestion(@RequestParam("questionId") int questionId) { 84 | if (hostHolder.getUser() == null) { 85 | return CommonUtils.getJSONString(999); 86 | } 87 | 88 | System.out.println("关注问题成功"); 89 | 90 | Question q = questionService.selectById(questionId); 91 | if (q == null) { 92 | return CommonUtils.getJSONString(1, "问题不存在"); 93 | } 94 | 95 | boolean ret = followService.follow(hostHolder.getUser().getId(), EntityType.ENTITY_QUESTION, questionId); 96 | 97 | eventProducer.fireEvent(new EventModel(EventType.FOLLOW) 98 | .setActorId(hostHolder.getUser().getId()).setEntityId(questionId) 99 | .setEntityType(EntityType.ENTITY_QUESTION).setEntityOwnerId(q.getUserId())); 100 | 101 | Map info = new HashMap<>(); 102 | info.put("headUrl", hostHolder.getUser().getHeadUrl()); 103 | info.put("name", hostHolder.getUser().getName()); 104 | info.put("id", hostHolder.getUser().getId()); 105 | info.put("count", followService.getFollowerCount(EntityType.ENTITY_QUESTION, questionId)); 106 | return CommonUtils.getJSONString(ret ? 0 : 1, info); 107 | 108 | } 109 | 110 | @RequestMapping(path = {"/unfollowQuestion"}, method = {RequestMethod.POST}) 111 | @ResponseBody 112 | public String unfollowQuestion(@RequestParam("questionId") int questionId) { 113 | if (hostHolder.getUser() == null) { 114 | return CommonUtils.getJSONString(999); 115 | } 116 | 117 | Question q = questionService.selectById(questionId); 118 | if (q == null) { 119 | return CommonUtils.getJSONString(1, "问题不存在"); 120 | } 121 | 122 | boolean ret = followService.unfollow(hostHolder.getUser().getId(), EntityType.ENTITY_QUESTION, questionId); 123 | 124 | eventProducer.fireEvent(new EventModel(EventType.UNFOLLOW) 125 | .setActorId(hostHolder.getUser().getId()).setEntityId(questionId) 126 | .setEntityType(EntityType.ENTITY_QUESTION).setEntityOwnerId(q.getUserId())); 127 | 128 | Map info = new HashMap<>(); 129 | info.put("id", hostHolder.getUser().getId()); 130 | info.put("count", followService.getFollowerCount(EntityType.ENTITY_QUESTION, questionId)); 131 | return CommonUtils.getJSONString(ret ? 0 : 1, info); 132 | } 133 | 134 | @RequestMapping(path = {"/user/{uid}/followers"}, method = {RequestMethod.GET}) 135 | public String followers(Model model, @PathVariable("uid") int userId) { 136 | List followerIds = followService.getFollowers(EntityType.ENTITY_USER, userId, 0, 10); 137 | if (hostHolder.getUser() != null) { 138 | model.addAttribute("followers", getUsersInfo(hostHolder.getUser().getId(), followerIds)); 139 | System.out.println( getUsersInfo(hostHolder.getUser().getId(), followerIds)); 140 | } else { 141 | model.addAttribute("followers", getUsersInfo(0, followerIds)); 142 | System.out.println(getUsersInfo(0, followerIds)); 143 | } 144 | model.addAttribute("followerCount", followService.getFollowerCount(EntityType.ENTITY_USER, userId)); 145 | model.addAttribute("curUser", userService.getUser(userId)); 146 | return "followers"; 147 | } 148 | 149 | @RequestMapping(path = {"/user/{uid}/followees"}, method = {RequestMethod.GET}) 150 | public String followees(Model model, @PathVariable("uid") int userId) { 151 | List followeeIds = followService.getFollowees(userId, EntityType.ENTITY_USER, 0, 10); 152 | 153 | if (hostHolder.getUser() != null) { 154 | model.addAttribute("followees", getUsersInfo(hostHolder.getUser().getId(), followeeIds)); 155 | } else { 156 | model.addAttribute("followees", getUsersInfo(0, followeeIds)); 157 | } 158 | model.addAttribute("followeeCount", followService.getFolloweeCount(userId, EntityType.ENTITY_USER)); 159 | model.addAttribute("curUser", userService.getUser(userId)); 160 | return "followees"; 161 | } 162 | 163 | private List getUsersInfo(int localUserId, List userIds) { 164 | List userInfos = new ArrayList(); 165 | for (Integer uid : userIds) { 166 | User user = userService.getUser(uid); 167 | if (user == null) { 168 | continue; 169 | } 170 | ViewObject vo = new ViewObject(); 171 | vo.set("user", user); 172 | vo.set("commentCount", commentService.getUserCommentCount(uid)); 173 | vo.set("followerCount", followService.getFollowerCount(EntityType.ENTITY_USER, uid)); 174 | vo.set("followeeCount", followService.getFolloweeCount(uid, EntityType.ENTITY_USER)); 175 | if (localUserId != 0) { 176 | vo.set("followed", followService.isFollower(localUserId, EntityType.ENTITY_USER, uid)); 177 | } else { 178 | vo.set("followed", false); 179 | } 180 | userInfos.add(vo); 181 | } 182 | return userInfos; 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/main/java/cn/fciasth/zhihu/util/JedisAdapter.java: -------------------------------------------------------------------------------- 1 | package cn.fciasth.zhihu.util; 2 | 3 | import cn.fciasth.zhihu.controller.CommentController; 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.*; 9 | 10 | import java.io.IOException; 11 | import java.util.List; 12 | import java.util.Set; 13 | 14 | @Service 15 | public class JedisAdapter implements InitializingBean{ 16 | 17 | private static final Logger logger = LoggerFactory.getLogger(JedisAdapter.class); 18 | 19 | private JedisPool jedisPool; 20 | 21 | @Override 22 | public void afterPropertiesSet() throws Exception { 23 | jedisPool = new JedisPool("redis://123.207.13.53:6379/2"); 24 | } 25 | 26 | public Jedis getJedis() { 27 | return jedisPool.getResource(); 28 | } 29 | 30 | public Transaction multi(Jedis jedis) { 31 | try { 32 | return jedis.multi(); 33 | } catch (Exception e) { 34 | logger.error("发生异常" + e.getMessage()); 35 | } finally { 36 | } 37 | return null; 38 | } 39 | 40 | public List exec(Transaction tx, Jedis jedis) { 41 | try { 42 | return tx.exec(); 43 | } catch (Exception e) { 44 | logger.error("发生异常" + e.getMessage()); 45 | tx.discard(); 46 | } finally { 47 | if (tx != null) { 48 | try { 49 | tx.close(); 50 | } catch (IOException ioe) { 51 | logger.error("发生异常" + ioe.getMessage()); 52 | } 53 | } 54 | 55 | if (jedis != null) { 56 | jedis.close(); 57 | } 58 | } 59 | return null; 60 | } 61 | 62 | public long zadd(String key, double score, String value) { 63 | Jedis jedis = null; 64 | try { 65 | jedis = jedisPool.getResource(); 66 | return jedis.zadd(key, score, value); 67 | } catch (Exception e) { 68 | logger.error("发生异常" + e.getMessage()); 69 | } finally { 70 | if (jedis != null) { 71 | jedis.close(); 72 | } 73 | } 74 | return 0; 75 | } 76 | 77 | public Set zrevrange(String key, int start, int end) { 78 | Jedis jedis = null; 79 | try { 80 | jedis = jedisPool.getResource(); 81 | return jedis.zrevrange(key, start, end); 82 | } catch (Exception e) { 83 | logger.error("发生异常" + e.getMessage()); 84 | } finally { 85 | if (jedis != null) { 86 | jedis.close(); 87 | } 88 | } 89 | return null; 90 | } 91 | 92 | public long zcard(String key) { 93 | Jedis jedis = null; 94 | try { 95 | jedis = jedisPool.getResource(); 96 | return jedis.zcard(key); 97 | } catch (Exception e) { 98 | logger.error("发生异常" + e.getMessage()); 99 | } finally { 100 | if (jedis != null) { 101 | jedis.close(); 102 | } 103 | } 104 | return 0; 105 | } 106 | 107 | public Double zscore(String key, String member) { 108 | Jedis jedis = null; 109 | try { 110 | jedis = jedisPool.getResource(); 111 | return jedis.zscore(key, member); 112 | } catch (Exception e) { 113 | logger.error("发生异常" + e.getMessage()); 114 | } finally { 115 | if (jedis != null) { 116 | jedis.close(); 117 | } 118 | } 119 | return null; 120 | } 121 | 122 | public long lpush(String key, String value) { 123 | Jedis jedis = null; 124 | try { 125 | jedis = jedisPool.getResource(); 126 | return jedis.lpush(key, value); 127 | } catch (Exception e) { 128 | logger.error("push发生异常" + e.getMessage()); 129 | } finally { 130 | if (jedis != null) { 131 | jedis.close(); 132 | } 133 | } 134 | return 0; 135 | } 136 | 137 | public List brpop(int timeout, String key) { 138 | Jedis jedis = null; 139 | try { 140 | jedis = jedisPool.getResource(); 141 | return jedis.brpop(timeout, key); 142 | } catch (Exception e) { 143 | logger.error("pop发生异常" + e.getMessage()); 144 | } finally { 145 | if (jedis != null) { 146 | jedis.close(); 147 | } 148 | } 149 | return null; 150 | } 151 | 152 | public long sadd(String key,String value){ 153 | Jedis jedis = null; 154 | try{ 155 | jedis = jedisPool.getResource(); 156 | return jedis.sadd(key,value); 157 | }catch (Exception e){ 158 | logger.error("redis添加失败"+e.getMessage()); 159 | }finally { 160 | if(jedis != null){ 161 | jedis.close(); 162 | } 163 | } 164 | return 0; 165 | } 166 | 167 | public long srem(String key,String value){ 168 | Jedis jedis = null; 169 | try{ 170 | jedis = jedisPool.getResource(); 171 | return jedis.srem(key,value); 172 | }catch (Exception e){ 173 | logger.error("redis删除失败"+e.getMessage()); 174 | }finally { 175 | if(jedis != null){ 176 | jedis.close(); 177 | } 178 | } 179 | return 0; 180 | } 181 | 182 | public long scard(String key){ 183 | Jedis jedis = null; 184 | try{ 185 | jedis = jedisPool.getResource(); 186 | return jedis.scard(key); 187 | }catch (Exception e){ 188 | logger.error("redis计数异常"+e.getMessage()); 189 | }finally { 190 | if(jedis != null){ 191 | jedis.close(); 192 | } 193 | } 194 | return 0; 195 | } 196 | 197 | public boolean sismember(String key,String value){ 198 | Jedis jedis = null; 199 | try{ 200 | jedis = jedisPool.getResource(); 201 | return jedis.sismember(key,value); 202 | }catch (Exception e){ 203 | logger.error("redis判断成员异常"+e.getMessage()); 204 | }finally { 205 | if(jedis != null){ 206 | jedis.close(); 207 | } 208 | } 209 | return false; 210 | } 211 | 212 | public static void print(int index,Object obj){ 213 | System.out.println(String.format("%d, %s",index,obj.toString())); 214 | } 215 | 216 | public static void main(String[] args) { 217 | Jedis jedis = new Jedis("redis://123.207.13.53:6379/1"); 218 | jedis.flushDB(); 219 | 220 | jedis.set("hello","world"); 221 | print(1,jedis.get("hello")); 222 | jedis.rename("hello","newhello"); 223 | print(2,jedis.get("newhello")); 224 | jedis.setex("hello2",15,"world"); 225 | 226 | jedis.set("pv","100"); 227 | jedis.incr("pv"); 228 | jedis.incrBy("pv",5); 229 | jedis.decrBy("pv",3); 230 | print(3,jedis.get("pv")); 231 | print(4,jedis.keys("*")); 232 | 233 | String listNmae = "list"; 234 | jedis.del(listNmae); 235 | for (int i = 0; i < 10; ++i){ 236 | jedis.lpush(listNmae,"a"+String.valueOf(i)); 237 | } 238 | print(4,jedis.lrange(listNmae,0,12)); 239 | print(5,jedis.lrange(listNmae,0,3)); 240 | 241 | print(6,jedis.llen(listNmae)); 242 | print(7,jedis.lpop(listNmae)); 243 | print(8,jedis.llen(listNmae)); 244 | print(9,jedis.lindex(listNmae,3)); 245 | print(10,jedis.linsert(listNmae, BinaryClient.LIST_POSITION.AFTER,"a4","bbb")); 246 | print(11,jedis.linsert(listNmae, BinaryClient.LIST_POSITION.BEFORE,"a4","xxx")); 247 | print(12,jedis.lrange(listNmae,0,12)); 248 | 249 | String userKey = "userxx"; 250 | jedis.hset(userKey,"name","jim"); 251 | jedis.hset(userKey,"age","12"); 252 | jedis.hset(userKey,"phone","12312323123"); 253 | print(13,jedis.hget(userKey,"name")); 254 | print(14,jedis.hgetAll(userKey)); 255 | jedis.hdel(userKey,"phone"); 256 | print(15,jedis.hgetAll(userKey)); 257 | print(16,jedis.hexists(userKey,"email")); 258 | print(17,jedis.hexists(userKey,"name")); 259 | print(18,jedis.hkeys(userKey)); 260 | print(19,jedis.hvals(userKey)); 261 | jedis.hsetnx(userKey,"school","zju"); 262 | jedis.hsetnx(userKey,"name","yxy"); 263 | print(20,jedis.hgetAll(userKey)); 264 | 265 | String likeKey1 = "commentLike1"; 266 | String likeKey2 = "commentLike2"; 267 | for (int i = 0; i < 10; i++){ 268 | jedis.sadd(likeKey1,String.valueOf(i)); 269 | jedis.sadd(likeKey2,String.valueOf(i*i)); 270 | } 271 | print(21,jedis.smembers(likeKey1)); 272 | print(22,jedis.smembers(likeKey2)); 273 | print(23,jedis.sunion(likeKey1,likeKey2)); 274 | print(24,jedis.sdiff(likeKey1,likeKey2)); 275 | print(25,jedis.sinter(likeKey1,likeKey2)); 276 | print(26,jedis.sismember(likeKey1,"12")); 277 | print(27,jedis.sismember(likeKey2,"16")); 278 | jedis.srem(likeKey1,"5"); 279 | print(28,jedis.smembers(likeKey1)); 280 | jedis.smove(likeKey2,likeKey1,"25"); 281 | print(29,jedis.smembers(likeKey1)); 282 | print(30,jedis.smembers(likeKey2)); 283 | print(31,jedis.scard(likeKey1)); 284 | 285 | String rankKey = "rankKey"; 286 | jedis.zadd(rankKey,15,"jim"); 287 | jedis.zadd(rankKey,60,"Ben"); 288 | jedis.zadd(rankKey,90,"Lee"); 289 | jedis.zadd(rankKey,75,"Lucy"); 290 | jedis.zadd(rankKey,80,"Mei"); 291 | print(32,jedis.zcard(rankKey)); 292 | print(33,jedis.zcount(rankKey,61,100)); 293 | print(34,jedis.zscore(rankKey,"Lucy")); 294 | jedis.zincrby(rankKey,2,"Lucy"); 295 | print(35,jedis.zscore(rankKey,"Lucy")); 296 | print(36,jedis.zrange(rankKey,0,100)); 297 | print(37,jedis.zrange(rankKey,1,3)); 298 | print(38,jedis.zrevrange(rankKey,1,3)); 299 | for (Tuple tuple: jedis.zrangeByScoreWithScores(rankKey,"60","100") 300 | ) { 301 | print(39,tuple.getElement()+":"+String.valueOf(tuple.getScore())); 302 | } 303 | print(40,jedis.zrank(rankKey,"Ben")); 304 | print(41,jedis.zrevrank(rankKey,"Ben")); 305 | 306 | String setKey = "zset"; 307 | jedis.zadd(setKey,1,"a"); 308 | jedis.zadd(setKey,1,"b"); 309 | jedis.zadd(setKey,1,"c"); 310 | jedis.zadd(setKey,1,"d"); 311 | jedis.zadd(setKey,1,"e"); 312 | print(42,jedis.zlexcount(setKey,"-","+")); 313 | print(42,jedis.zlexcount(setKey,"[b","[d")); 314 | print(42,jedis.zlexcount(setKey,"(b","[d")); 315 | jedis.zremrangeByLex(setKey,"(c","+"); 316 | print(43,jedis.zrange(setKey,0,2)); 317 | 318 | JedisPool pool = new JedisPool("redis://123.207.13.53:6379/1"); 319 | for (int i = 0; i < 100; ++i){ 320 | Jedis j = pool.getResource(); 321 | print(44,j.get("pv")); 322 | j.close(); 323 | } 324 | 325 | } 326 | 327 | 328 | } 329 | --------------------------------------------------------------------------------