├── http ├── demo.http ├── test.http └── ebook.http ├── web ├── .browserslistrc ├── public │ ├── favicon.ico │ ├── image │ │ ├── vue.jpeg │ │ ├── cover1.png │ │ ├── cover2.png │ │ ├── django.jpeg │ │ ├── html.jpeg │ │ ├── idea.jpeg │ │ ├── java.jpeg │ │ ├── loading.gif │ │ ├── mysql.jpeg │ │ ├── oracle.jpeg │ │ ├── python.jpeg │ │ ├── spring.jpeg │ │ ├── system.jpg │ │ ├── leetcode.jpeg │ │ ├── network.jpeg │ │ ├── pycharm.jpeg │ │ ├── springboot.jpeg │ │ └── visual_studio_code.jpeg │ ├── js │ │ └── session-storage.js │ ├── reset.less │ └── index.html ├── src │ ├── assets │ │ └── logo.png │ ├── shims-vue.d.ts │ ├── store │ │ └── index.ts │ ├── views │ │ ├── about.vue │ │ ├── home.vue │ │ ├── admin │ │ │ └── admin-category.vue │ │ └── doc.vue │ ├── App.vue │ ├── components │ │ └── the-footer.vue │ ├── main.ts │ ├── router │ │ └── index.ts │ └── util │ │ └── tool.ts ├── .env.dev ├── .env.prod ├── .gitignore ├── README.md ├── vue.config.js ├── .eslintrc.js ├── tsconfig.json └── package.json ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ ├── maven-wrapper.properties │ └── MavenWrapperDownloader.java ├── README.assets ├── image-20220512113447505.png ├── image-20220512113524408.png ├── image-20220512113629406.png ├── image-20220512113703751.png └── image-20220512113745011.png ├── src ├── main │ ├── java │ │ └── com │ │ │ └── xiaolang │ │ │ └── wiki │ │ │ ├── req │ │ │ ├── DocQueryReq.java │ │ │ ├── CategoryQueryReq.java │ │ │ ├── UserQueryReq.java │ │ │ ├── PageReq.java │ │ │ ├── EbookQueryReq.java │ │ │ ├── UserResetPasswordReq.java │ │ │ ├── UserLoginReq.java │ │ │ ├── CategorySaveReq.java │ │ │ ├── UserSaveReq.java │ │ │ ├── DocSaveReq.java │ │ │ └── EbookSaveReq.java │ │ │ ├── mapper │ │ │ ├── TestMapper.java │ │ │ ├── DocMapperCust.java │ │ │ ├── EbookSnapshotMapperCust.java │ │ │ ├── DocMapper.java │ │ │ ├── DemoMapper.java │ │ │ ├── UserMapper.java │ │ │ ├── EbookMapper.java │ │ │ ├── CategoryMapper.java │ │ │ ├── EbookSnapshotMapper.java │ │ │ └── ContentMapper.java │ │ │ ├── util │ │ │ ├── RequestContext.java │ │ │ ├── RedisUtil.java │ │ │ ├── CopyUtil.java │ │ │ └── SnowFlake.java │ │ │ ├── config │ │ │ ├── WebSocketConfig.java │ │ │ ├── CorsConfig.java │ │ │ ├── WikiApplication.java │ │ │ ├── SpringMvcConfig.java │ │ │ └── JacksonConfig.java │ │ │ ├── service │ │ │ ├── TestService.java │ │ │ ├── DemoService.java │ │ │ ├── WsService.java │ │ │ ├── EbookSnapshotService.java │ │ │ ├── EbookService.java │ │ │ ├── CategoryService.java │ │ │ ├── UserService.java │ │ │ └── DocService.java │ │ │ ├── exception │ │ │ ├── BusinessExceptionCode.java │ │ │ └── BusinessException.java │ │ │ ├── domain │ │ │ ├── Test.java │ │ │ ├── Demo.java │ │ │ ├── Content.java │ │ │ ├── Category.java │ │ │ ├── User.java │ │ │ ├── Doc.java │ │ │ ├── EbookSnapshot.java │ │ │ └── Ebook.java │ │ │ ├── controller │ │ │ ├── DemoController.java │ │ │ ├── TestController.java │ │ │ ├── EbookSnapshotController.java │ │ │ ├── EbookController.java │ │ │ ├── CategoryController.java │ │ │ ├── DocController.java │ │ │ ├── ControllerExceptionHandler.java │ │ │ └── UserController.java │ │ │ ├── resp │ │ │ ├── PageResp.java │ │ │ ├── UserLoginResp.java │ │ │ ├── CommonResp.java │ │ │ ├── CategoryQueryResp.java │ │ │ ├── UserQueryResp.java │ │ │ ├── StatisticResp.java │ │ │ ├── DocQueryResp.java │ │ │ └── EbookQueryResp.java │ │ │ ├── job │ │ │ ├── DocJob.java │ │ │ ├── EbookSnapshotJob.java │ │ │ └── TestJob.java │ │ │ ├── rocketmq │ │ │ └── VoteTopicConsumer.java │ │ │ ├── filter │ │ │ └── LogFilter.java │ │ │ ├── interceptor │ │ │ ├── LogInterceptor.java │ │ │ └── LoginInterceptor.java │ │ │ ├── websocket │ │ │ └── WebSocketServer.java │ │ │ └── aspect │ │ │ └── LogAspect.java │ └── resources │ │ ├── mapper │ │ ├── TestMapper.xml │ │ ├── DocMapperCust.xml │ │ ├── EbookSnapshotMapperCust.xml │ │ ├── DemoMapper.xml │ │ ├── ContentMapper.xml │ │ └── CategoryMapper.xml │ │ ├── application-prod.properties │ │ ├── application.properties │ │ ├── banner.txt │ │ ├── generator │ │ └── generator-config.xml │ │ └── logback-spring.xml └── test │ └── java │ └── com │ └── wzy │ └── wiki │ └── WikiApplicationTests.java ├── doc └── deploy │ ├── web.conf │ ├── deploy.sh │ └── server.conf ├── .gitignore ├── README.md ├── pom.xml └── mvnw.cmd /http/demo.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:8080/demo/list 2 | 3 | ### -------------------------------------------------------------------------------- /web/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianTianNo1/wiki_vue3/HEAD/web/public/favicon.ico -------------------------------------------------------------------------------- /web/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianTianNo1/wiki_vue3/HEAD/web/src/assets/logo.png -------------------------------------------------------------------------------- /web/public/image/vue.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianTianNo1/wiki_vue3/HEAD/web/public/image/vue.jpeg -------------------------------------------------------------------------------- /web/public/image/cover1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianTianNo1/wiki_vue3/HEAD/web/public/image/cover1.png -------------------------------------------------------------------------------- /web/public/image/cover2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianTianNo1/wiki_vue3/HEAD/web/public/image/cover2.png -------------------------------------------------------------------------------- /web/public/image/django.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianTianNo1/wiki_vue3/HEAD/web/public/image/django.jpeg -------------------------------------------------------------------------------- /web/public/image/html.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianTianNo1/wiki_vue3/HEAD/web/public/image/html.jpeg -------------------------------------------------------------------------------- /web/public/image/idea.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianTianNo1/wiki_vue3/HEAD/web/public/image/idea.jpeg -------------------------------------------------------------------------------- /web/public/image/java.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianTianNo1/wiki_vue3/HEAD/web/public/image/java.jpeg -------------------------------------------------------------------------------- /web/public/image/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianTianNo1/wiki_vue3/HEAD/web/public/image/loading.gif -------------------------------------------------------------------------------- /web/public/image/mysql.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianTianNo1/wiki_vue3/HEAD/web/public/image/mysql.jpeg -------------------------------------------------------------------------------- /web/public/image/oracle.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianTianNo1/wiki_vue3/HEAD/web/public/image/oracle.jpeg -------------------------------------------------------------------------------- /web/public/image/python.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianTianNo1/wiki_vue3/HEAD/web/public/image/python.jpeg -------------------------------------------------------------------------------- /web/public/image/spring.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianTianNo1/wiki_vue3/HEAD/web/public/image/spring.jpeg -------------------------------------------------------------------------------- /web/public/image/system.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianTianNo1/wiki_vue3/HEAD/web/public/image/system.jpg -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianTianNo1/wiki_vue3/HEAD/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /web/.env.dev: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | VUE_APP_SERVER=http://localhost:8097 3 | VUE_APP_WS_SERVER=ws://127.0.0.1:8097 -------------------------------------------------------------------------------- /web/public/image/leetcode.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianTianNo1/wiki_vue3/HEAD/web/public/image/leetcode.jpeg -------------------------------------------------------------------------------- /web/public/image/network.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianTianNo1/wiki_vue3/HEAD/web/public/image/network.jpeg -------------------------------------------------------------------------------- /web/public/image/pycharm.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianTianNo1/wiki_vue3/HEAD/web/public/image/pycharm.jpeg -------------------------------------------------------------------------------- /web/public/image/springboot.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianTianNo1/wiki_vue3/HEAD/web/public/image/springboot.jpeg -------------------------------------------------------------------------------- /web/.env.prod: -------------------------------------------------------------------------------- 1 | NODE_ENV=production 2 | VUE_APP_SERVER=http://120.25.249.159:8097 3 | VUE_APP_WS_SERVER=ws://120.25.249.159:8097 -------------------------------------------------------------------------------- /web/public/image/visual_studio_code.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianTianNo1/wiki_vue3/HEAD/web/public/image/visual_studio_code.jpeg -------------------------------------------------------------------------------- /README.assets/image-20220512113447505.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianTianNo1/wiki_vue3/HEAD/README.assets/image-20220512113447505.png -------------------------------------------------------------------------------- /README.assets/image-20220512113524408.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianTianNo1/wiki_vue3/HEAD/README.assets/image-20220512113524408.png -------------------------------------------------------------------------------- /README.assets/image-20220512113629406.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianTianNo1/wiki_vue3/HEAD/README.assets/image-20220512113629406.png -------------------------------------------------------------------------------- /README.assets/image-20220512113703751.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianTianNo1/wiki_vue3/HEAD/README.assets/image-20220512113703751.png -------------------------------------------------------------------------------- /README.assets/image-20220512113745011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LianTianNo1/wiki_vue3/HEAD/README.assets/image-20220512113745011.png -------------------------------------------------------------------------------- /http/test.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:8080/hello 2 | Accept: application/json 3 | 4 | ### 5 | 6 | GET http://localhost:8080/test/list 7 | 8 | ### -------------------------------------------------------------------------------- /http/ebook.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:8097/ebook/list?page=1&size=1001 2 | 3 | ### 4 | 5 | POST http://localhost:8097/ebook/save 6 | 7 | {} 8 | 9 | ### -------------------------------------------------------------------------------- /web/src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | declare module '*.vue' { 3 | import type { DefineComponent } from 'vue' 4 | const component: DefineComponent<{}, {}, any> 5 | export default component 6 | } 7 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/req/DocQueryReq.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.req; 2 | 3 | public class DocQueryReq extends PageReq { 4 | @Override 5 | public String toString() { 6 | return "DocQueryReq{} " + super.toString(); 7 | } 8 | } -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/mapper/TestMapper.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.mapper; 2 | 3 | import com.xiaolang.wiki.domain.Test; 4 | 5 | import java.util.List; 6 | 7 | public interface TestMapper { 8 | 9 | public List list(); 10 | } 11 | -------------------------------------------------------------------------------- /doc/deploy/web.conf: -------------------------------------------------------------------------------- 1 | server{ 2 | listen 80; 3 | # server_name 8.133.184.84; 4 | server_name 182.254.157.208; 5 | 6 | location / { 7 | alias /home/ubuntu/web; 8 | index index.html; 9 | try_files $uri $uri/ /index.html; 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/req/CategoryQueryReq.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.req; 2 | 3 | public class CategoryQueryReq extends PageReq { 4 | @Override 5 | public String toString() { 6 | return "CategoryQueryReq{} " + super.toString(); 7 | } 8 | } -------------------------------------------------------------------------------- /src/test/java/com/wzy/wiki/WikiApplicationTests.java: -------------------------------------------------------------------------------- 1 | //package com.xiaolang.wiki; 2 | // 3 | //import org.junit.jupiter.api.Test; 4 | //import org.springframework.boot.test.context.SpringBootTest; 5 | // 6 | //@SpringBootTest 7 | //class WikiApplicationTests { 8 | // 9 | // @Test 10 | // void contextLoads() { 11 | // } 12 | // 13 | //} 14 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /doc/deploy/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "publish----------" 3 | 4 | process_id=`ps -ef | grep wiki.jar | grep -v grep |awk '{print $2}'` 5 | if [ $process_id ] ; then 6 | sudo kill -9 $process_id 7 | fi 8 | 9 | source /etc/profile 10 | nohup java -jar -Dspring.profiles.active=prod ~/wiki/wiki.jar > /dev/null 2>&1 & 11 | 12 | echo "end publish" 13 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/mapper/DocMapperCust.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.mapper; 2 | 3 | import org.apache.ibatis.annotations.Param; 4 | 5 | public interface DocMapperCust { 6 | 7 | public void increaseViewCount(@Param("id") Long id); 8 | 9 | public void increaseVoteCount(@Param("id") Long id); 10 | 11 | public void updateEbookInfo(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/mapper/EbookSnapshotMapperCust.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.mapper; 2 | 3 | 4 | import com.xiaolang.wiki.resp.StatisticResp; 5 | 6 | import java.util.List; 7 | 8 | public interface EbookSnapshotMapperCust { 9 | 10 | public void genSnapshot(); 11 | 12 | List getStatistic(); 13 | 14 | List get30Statistic(); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/resources/mapper/TestMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /web/README.md: -------------------------------------------------------------------------------- 1 | # web 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | npm run lint 21 | ``` 22 | 23 | ### Customize configuration 24 | See [Configuration Reference](https://cli.vuejs.org/config/). 25 | -------------------------------------------------------------------------------- /web/src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { createStore } from 'vuex' 2 | 3 | declare let SessionStorage: any; 4 | const USER = "USER"; 5 | 6 | const store = createStore({ 7 | state: { 8 | user: SessionStorage.get(USER) || {} 9 | }, 10 | mutations: { 11 | setUser (state, user) { 12 | state.user = user; 13 | SessionStorage.set(USER,user); 14 | } 15 | }, 16 | actions: { 17 | }, 18 | modules: { 19 | } 20 | }) 21 | 22 | export default store; 23 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/util/RequestContext.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.util; 2 | 3 | import java.io.Serializable; 4 | 5 | public class RequestContext implements Serializable { 6 | private static ThreadLocal remoteAddr = new ThreadLocal<>(); 7 | 8 | public static String getRemoteAddr() { 9 | return remoteAddr.get(); 10 | } 11 | 12 | public static void setRemoteAddr(String remoteAddr) { 13 | RequestContext.remoteAddr.set(remoteAddr); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/config/WebSocketConfig.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.socket.server.standard.ServerEndpointExporter; 6 | 7 | @Configuration 8 | public class WebSocketConfig { 9 | 10 | @Bean 11 | public ServerEndpointExporter serverEndpointExporter() { 12 | return new ServerEndpointExporter(); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/service/TestService.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.service; 2 | 3 | import com.xiaolang.wiki.domain.Test; 4 | import com.xiaolang.wiki.mapper.TestMapper; 5 | import org.springframework.stereotype.Service; 6 | 7 | import javax.annotation.Resource; 8 | import java.util.List; 9 | 10 | @Service 11 | public class TestService { 12 | 13 | @Resource 14 | private TestMapper testMapper; 15 | 16 | public List list(){ 17 | return testMapper.list(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/service/DemoService.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.service; 2 | 3 | import com.xiaolang.wiki.domain.Demo; 4 | import com.xiaolang.wiki.mapper.DemoMapper; 5 | import org.springframework.stereotype.Service; 6 | 7 | import javax.annotation.Resource; 8 | import java.util.List; 9 | 10 | @Service 11 | public class DemoService { 12 | 13 | @Resource 14 | private DemoMapper demoMapper; 15 | 16 | public List list(){ 17 | return demoMapper.selectByExample(null); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /web/vue.config.js: -------------------------------------------------------------------------------- 1 | // vue.config.js for less-loader@6.0.0 2 | module.exports = { 3 | css: { 4 | loaderOptions: { 5 | less: { 6 | lessOptions: { 7 | modifyVars: { 8 | 'primary-color': '#ff5d8f', 9 | 'link-color': '#ff5d8f', 10 | 'border-radius-base': '2px', 11 | }, 12 | javascriptEnabled: true, 13 | }, 14 | }, 15 | }, 16 | }, 17 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /log/ 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /web/public/js/session-storage.js: -------------------------------------------------------------------------------- 1 | SessionStorage = { 2 | get: function (key) { 3 | var v = sessionStorage.getItem(key); 4 | if (v && typeof(v) !== "undefined" && v !== "undefined") { 5 | return JSON.parse(v); 6 | } 7 | }, 8 | set: function (key, data) { 9 | sessionStorage.setItem(key, JSON.stringify(data)); 10 | }, 11 | remove: function (key) { 12 | sessionStorage.removeItem(key); 13 | }, 14 | clearAll: function () { 15 | sessionStorage.clear(); 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/req/UserQueryReq.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.req; 2 | 3 | public class UserQueryReq extends PageReq { 4 | 5 | private String loginName; 6 | 7 | public String getLoginName() { 8 | return loginName; 9 | } 10 | 11 | public void setLoginName(String loginName) { 12 | this.loginName = loginName; 13 | } 14 | 15 | @Override 16 | public String toString() { 17 | return "UserQueryReq{" + 18 | "loginName='" + loginName + '\'' + 19 | '}'; 20 | } 21 | } -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/exception/BusinessExceptionCode.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.exception; 2 | 3 | public enum BusinessExceptionCode { 4 | 5 | USER_LOGIN_NAME_EXIST("登录名已存在"), 6 | 7 | LOGIN_USER_ERROR("用户名不存在或密码错误"), 8 | 9 | VOTE_REPEAT("您已点赞过") 10 | ; 11 | 12 | private String desc; 13 | 14 | BusinessExceptionCode(String desc) { 15 | this.desc = desc; 16 | } 17 | 18 | public String getDesc() { 19 | return desc; 20 | } 21 | 22 | public void setDesc(String desc) { 23 | this.desc = desc; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /doc/deploy/server.conf: -------------------------------------------------------------------------------- 1 | server{ 2 | listen 8097; 3 | server_name 120.25.249.159/; 4 | 5 | location / { 6 | proxy_pass http://localhost:8097; 7 | 8 | # 针对websocket,需要增加下面的配置 9 | proxy_redirect off; 10 | proxy_http_version 1.1; 11 | proxy_set_header Upgrade $http_upgrade; 12 | proxy_set_header Connection "upgrade"; 13 | proxy_set_header Host $host:$server_port; 14 | proxy_set_header X-Real-IP $remote_addr; 15 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 16 | 17 | # 代理时长设置600秒,默认60秒,websocket超时会自动断开 18 | proxy_read_timeout 600s; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/service/WsService.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.service; 2 | 3 | import com.xiaolang.wiki.websocket.WebSocketServer; 4 | import org.slf4j.MDC; 5 | import org.springframework.scheduling.annotation.Async; 6 | import org.springframework.stereotype.Service; 7 | 8 | import javax.annotation.Resource; 9 | 10 | @Service 11 | public class WsService { 12 | 13 | @Resource 14 | public WebSocketServer webSocketServer; 15 | 16 | @Async 17 | public void sendInfo(String message, String logId) { 18 | MDC.put("LOG_ID", logId); 19 | webSocketServer.sendInfo(message); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/exception/BusinessException.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.exception; 2 | 3 | public class BusinessException extends RuntimeException{ 4 | 5 | private BusinessExceptionCode code; 6 | 7 | public BusinessException (BusinessExceptionCode code) { 8 | super(code.getDesc()); 9 | this.code = code; 10 | } 11 | 12 | public BusinessExceptionCode getCode() { 13 | return code; 14 | } 15 | 16 | public void setCode(BusinessExceptionCode code) { 17 | this.code = code; 18 | } 19 | 20 | /** 21 | * 不写入堆栈信息,提高性能 22 | */ 23 | @Override 24 | public Throwable fillInStackTrace() { 25 | return this; 26 | } 27 | } -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/domain/Test.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.domain; 2 | 3 | public class Test { 4 | 5 | private Integer id; 6 | 7 | private String name; 8 | 9 | private String password; 10 | 11 | public Integer getId() { 12 | return id; 13 | } 14 | 15 | public void setId(Integer id) { 16 | this.id = id; 17 | } 18 | 19 | public String getName() { 20 | return name; 21 | } 22 | 23 | public void setName(String name) { 24 | this.name = name; 25 | } 26 | 27 | public String getPassword() { 28 | return password; 29 | } 30 | 31 | public void setPassword(String password) { 32 | this.password = password; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/controller/DemoController.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.controller; 2 | 3 | import com.xiaolang.wiki.domain.Demo; 4 | import com.xiaolang.wiki.service.DemoService; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | import javax.annotation.Resource; 10 | import java.util.List; 11 | 12 | @RestController 13 | @RequestMapping("/demo") 14 | public class DemoController { 15 | 16 | @Resource 17 | private DemoService demoService; 18 | 19 | @GetMapping("/list") 20 | public List list(){ 21 | return demoService.list(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/resources/application-prod.properties: -------------------------------------------------------------------------------- 1 | # 增加数据库连接 2 | spring.datasource.url=jdbc:mysql://localhost:3307/wiki3?characterEncoding=UTF8&autoReconnect=true&serverTimezone=Asia/Shanghai&allowMultiQueries=true 3 | spring.datasource.username=root 4 | spring.datasource.password=8426826TL 5 | spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 6 | 7 | # 配置mybatis所有的Mapper.xml所在的路径 8 | mybatis.mapper-locations=classpath:/mapper/**/*.xml 9 | 10 | # 打印所有的sql日志:sql,参数,结果 11 | logging.level.com.xiaolang.wiki.mapper=trace 12 | 13 | # redis配置 14 | spring.redis.host=localhost 15 | spring.redis.port=6379 16 | # spring.redis.password=lang 17 | 18 | # RocketMQ配置 19 | #rocketmq.name-server=127.0.0.1:9876 20 | #rocketmq.producer.group=default 21 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/resp/PageResp.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.resp; 2 | 3 | import java.util.List; 4 | 5 | public class PageResp { 6 | private long total; 7 | 8 | private List list; 9 | 10 | public long getTotal() { 11 | return total; 12 | } 13 | 14 | public void setTotal(long total) { 15 | this.total = total; 16 | } 17 | 18 | public List getList() { 19 | return list; 20 | } 21 | 22 | public void setList(List list) { 23 | this.list = list; 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | return "PageResp{" + 29 | "total=" + total + 30 | ", list=" + list + 31 | '}'; 32 | } 33 | } -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8097 2 | 3 | # 增加数据库连接 4 | spring.datasource.url=jdbc:mysql://localhost:3307/wiki3?characterEncoding=UTF8&autoReconnect=true&serverTimezone=Asia/Shanghai&allowMultiQueries=true 5 | spring.datasource.username=root 6 | spring.datasource.password=8426826TL 7 | spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 8 | 9 | # 配置mybatis所有的Mapper.xml所在的路径 10 | mybatis.mapper-locations=classpath:/mapper/**/*.xml 11 | 12 | # 打印所有的sql日志:sql,参数,结果 13 | logging.level.com.xiaolang.wiki.mapper=trace 14 | 15 | # redis配置 16 | spring.redis.host=localhost 17 | spring.redis.port=6379 18 | # spring.redis.password=lang 19 | 20 | # RocketMQ配置 21 | #rocketmq.name-server=127.0.0.1:9876 22 | #rocketmq.producer.group=default 23 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/controller/TestController.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.controller; 2 | 3 | import com.xiaolang.wiki.domain.Test; 4 | import com.xiaolang.wiki.service.TestService; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | import javax.annotation.Resource; 9 | import java.util.List; 10 | 11 | @RestController 12 | public class TestController { 13 | 14 | @Resource 15 | private TestService testService; 16 | 17 | @GetMapping("/hello") 18 | public String hello(){ 19 | return "HelloWorld!"; 20 | } 21 | 22 | @GetMapping("/test/list") 23 | public List list(){ 24 | return testService.list(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /web/public/reset.less: -------------------------------------------------------------------------------- 1 | @import '~ant-design-vue/dist/antd.less'; 2 | 3 | .ant-btn { 4 | border-radius: 16px; 5 | } 6 | .ant-input { 7 | border-radius: 16px; 8 | } 9 | 10 | // 修改滚动条样式 11 | ::-webkit-scrollbar 12 | { 13 | width: 5px; 14 | } 15 | 16 | /*定义滚动条轨道 内阴影+圆角*/ 17 | ::-webkit-scrollbar-track 18 | { 19 | border-radius: 10px; 20 | background-color: rgba(0,0,0,0.1); 21 | } 22 | 23 | /*定义滑块 内阴影+圆角*/ 24 | ::-webkit-scrollbar-thumb 25 | { 26 | border-radius: 10px; 27 | -webkit-box-shadow: inset 0 0 6px #5f9746; 28 | background-color: rgba(0,0,0,0.1); 29 | } 30 | 31 | 32 | 33 | /* 34 | 35 | // wangEditor编辑框高度 36 | .w-e-text-container{ 37 | height: 420px !important;!*!important是重点,因为原div是行内样式设置的高度300px*! 38 | max-height:800px !important; 39 | }*/ 40 | -------------------------------------------------------------------------------- /web/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/vue3-essential', 8 | 'eslint:recommended', 9 | '@vue/typescript/recommended' 10 | ], 11 | parserOptions: { 12 | ecmaVersion: 2020 13 | }, 14 | rules: { 15 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 16 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 17 | 'vue/no-deprecated-slot-attribute': 'off', 18 | 'vue/no-unused-components': 'off', 19 | 'vue/no-unused-vars': 'off', 20 | '@typescript-eslint/no-unused-vars': 'off', 21 | '@typescript-eslint/no-explicit-any': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/ban-types': 'off' 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /web/src/views/about.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "skipLibCheck": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "baseUrl": ".", 14 | "types": [ 15 | "webpack-env" 16 | ], 17 | "paths": { 18 | "@/*": [ 19 | "src/*" 20 | ] 21 | }, 22 | "lib": [ 23 | "esnext", 24 | "dom", 25 | "dom.iterable", 26 | "scripthost" 27 | ] 28 | }, 29 | "include": [ 30 | "src/**/*.ts", 31 | "src/**/*.tsx", 32 | "src/**/*.vue", 33 | "tests/**/*.ts", 34 | "tests/**/*.tsx" 35 | ], 36 | "exclude": [ 37 | "node_modules" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/config/CorsConfig.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.cors.CorsConfiguration; 5 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 6 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 7 | 8 | @Configuration 9 | public class CorsConfig implements WebMvcConfigurer { 10 | 11 | @Override 12 | public void addCorsMappings(CorsRegistry registry) { 13 | registry.addMapping("/**") 14 | .allowedOriginPatterns("*") 15 | .allowedHeaders(CorsConfiguration.ALL) 16 | .allowedMethods(CorsConfiguration.ALL) 17 | .allowCredentials(true) 18 | .maxAge(3600); // 1小时内不需要再预检(发OPTIONS请求) 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | _ooOoo_ 2 | o8888888o 3 | 88" . "88 4 | (| ^_^ |) 5 | O\ = /O 6 | ____/`---'\____ 7 | .' \\| |// `. 8 | / \\||| : |||// \ 9 | / _||||| -:- |||||- \ 10 | | | \\\ - /// | | 11 | | \_| ''\---/'' | | 12 | \ .-\__ `-` ___/-. / 13 | ___`. .' /--.--\ `. . ___ 14 | ."" '< `.___\_<|>_/___.' >'"". 15 | | | : `- \`.;`\ _ /`;.`/ - ` : | | 16 | \ \ `-. \_ __\ /__ _/ .-` / / 17 | ========`-.____`-.___\_____/___.-`____.-'======== 18 | `=---=' 19 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 20 | 佛祖保佑 永不宕机 永无BUG -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/domain/Demo.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.domain; 2 | 3 | public class Demo { 4 | private Long id; 5 | 6 | private String name; 7 | 8 | public Long getId() { 9 | return id; 10 | } 11 | 12 | public void setId(Long id) { 13 | this.id = id; 14 | } 15 | 16 | public String getName() { 17 | return name; 18 | } 19 | 20 | public void setName(String name) { 21 | this.name = name; 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | StringBuilder sb = new StringBuilder(); 27 | sb.append(getClass().getSimpleName()); 28 | sb.append(" ["); 29 | sb.append("Hash = ").append(hashCode()); 30 | sb.append(", id=").append(id); 31 | sb.append(", name=").append(name); 32 | sb.append("]"); 33 | return sb.toString(); 34 | } 35 | } -------------------------------------------------------------------------------- /src/main/resources/mapper/DocMapperCust.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | update doc set view_count = view_count + 1 where id = #{id} 7 | 8 | 9 | 10 | update doc set vote_count = vote_count + 1 where id = #{id} 11 | 12 | 13 | 14 | update ebook t1, (select ebook_id, count(1) doc_count, sum(view_count) view_count, sum(vote_count) vote_count from doc group by ebook_id) t2 15 | set t1.doc_count = t2.doc_count, t1.view_count = t2.view_count, t1.vote_count = t2.vote_count 16 | where t1.id = t2.ebook_id 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/domain/Content.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.domain; 2 | 3 | public class Content { 4 | private Long id; 5 | 6 | private String content; 7 | 8 | public Long getId() { 9 | return id; 10 | } 11 | 12 | public void setId(Long id) { 13 | this.id = id; 14 | } 15 | 16 | public String getContent() { 17 | return content; 18 | } 19 | 20 | public void setContent(String content) { 21 | this.content = content; 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | StringBuilder sb = new StringBuilder(); 27 | sb.append(getClass().getSimpleName()); 28 | sb.append(" ["); 29 | sb.append("Hash = ").append(hashCode()); 30 | sb.append(", id=").append(id); 31 | sb.append(", content=").append(content); 32 | sb.append("]"); 33 | return sb.toString(); 34 | } 35 | } -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/req/PageReq.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.req; 2 | 3 | import javax.validation.constraints.Max; 4 | import javax.validation.constraints.NotNull; 5 | 6 | public class PageReq { 7 | @NotNull(message = "【页码】不能为空") 8 | private int page; 9 | 10 | @NotNull(message = "【每页条数】不能为空") 11 | @Max(value = 1000,message = "【每页条数】不能超过1000") 12 | private int size; 13 | 14 | public int getPage() { 15 | return page; 16 | } 17 | 18 | public void setPage(int page) { 19 | this.page = page; 20 | } 21 | 22 | public int getSize() { 23 | return size; 24 | } 25 | 26 | public void setSize(int size) { 27 | this.size = size; 28 | } 29 | 30 | @Override 31 | public String toString() { 32 | return "PageReq{" + 33 | "page=" + page + 34 | ", size=" + size + 35 | '}'; 36 | } 37 | } -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/mapper/DocMapper.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.mapper; 2 | 3 | import com.xiaolang.wiki.domain.Doc; 4 | import com.xiaolang.wiki.domain.DocExample; 5 | import java.util.List; 6 | import org.apache.ibatis.annotations.Param; 7 | 8 | public interface DocMapper { 9 | long countByExample(DocExample example); 10 | 11 | int deleteByExample(DocExample example); 12 | 13 | int deleteByPrimaryKey(Long id); 14 | 15 | int insert(Doc record); 16 | 17 | int insertSelective(Doc record); 18 | 19 | List selectByExample(DocExample example); 20 | 21 | Doc selectByPrimaryKey(Long id); 22 | 23 | int updateByExampleSelective(@Param("record") Doc record, @Param("example") DocExample example); 24 | 25 | int updateByExample(@Param("record") Doc record, @Param("example") DocExample example); 26 | 27 | int updateByPrimaryKeySelective(Doc record); 28 | 29 | int updateByPrimaryKey(Doc record); 30 | } -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/mapper/DemoMapper.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.mapper; 2 | 3 | import com.xiaolang.wiki.domain.Demo; 4 | import com.xiaolang.wiki.domain.DemoExample; 5 | import java.util.List; 6 | import org.apache.ibatis.annotations.Param; 7 | 8 | public interface DemoMapper { 9 | long countByExample(DemoExample example); 10 | 11 | int deleteByExample(DemoExample example); 12 | 13 | int deleteByPrimaryKey(Long id); 14 | 15 | int insert(Demo record); 16 | 17 | int insertSelective(Demo record); 18 | 19 | List selectByExample(DemoExample example); 20 | 21 | Demo selectByPrimaryKey(Long id); 22 | 23 | int updateByExampleSelective(@Param("record") Demo record, @Param("example") DemoExample example); 24 | 25 | int updateByExample(@Param("record") Demo record, @Param("example") DemoExample example); 26 | 27 | int updateByPrimaryKeySelective(Demo record); 28 | 29 | int updateByPrimaryKey(Demo record); 30 | } -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/mapper/UserMapper.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.mapper; 2 | 3 | import com.xiaolang.wiki.domain.User; 4 | import com.xiaolang.wiki.domain.UserExample; 5 | import java.util.List; 6 | import org.apache.ibatis.annotations.Param; 7 | 8 | public interface UserMapper { 9 | long countByExample(UserExample example); 10 | 11 | int deleteByExample(UserExample example); 12 | 13 | int deleteByPrimaryKey(Long id); 14 | 15 | int insert(User record); 16 | 17 | int insertSelective(User record); 18 | 19 | List selectByExample(UserExample example); 20 | 21 | User selectByPrimaryKey(Long id); 22 | 23 | int updateByExampleSelective(@Param("record") User record, @Param("example") UserExample example); 24 | 25 | int updateByExample(@Param("record") User record, @Param("example") UserExample example); 26 | 27 | int updateByPrimaryKeySelective(User record); 28 | 29 | int updateByPrimaryKey(User record); 30 | } -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/service/EbookSnapshotService.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.service; 2 | 3 | import com.xiaolang.wiki.mapper.EbookSnapshotMapperCust; 4 | import com.xiaolang.wiki.resp.StatisticResp; 5 | import org.springframework.stereotype.Service; 6 | 7 | import javax.annotation.Resource; 8 | import java.util.List; 9 | 10 | @Service 11 | public class EbookSnapshotService { 12 | 13 | @Resource 14 | private EbookSnapshotMapperCust ebookSnapshotMapperCust; 15 | 16 | public void genSnapshot() { 17 | ebookSnapshotMapperCust.genSnapshot(); 18 | } 19 | 20 | /** 21 | * 获取首页数值数据:总阅读数、总点赞数、今日阅读数、今日点赞数、今日预计阅读数、今日预计阅读增长 22 | */ 23 | public List getStatistic() { 24 | return ebookSnapshotMapperCust.getStatistic(); 25 | } 26 | 27 | /* 28 | * 30天数值统计 29 | ` */ 30 | public List get30Statistic() { 31 | return ebookSnapshotMapperCust.get30Statistic(); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/mapper/EbookMapper.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.mapper; 2 | 3 | import com.xiaolang.wiki.domain.Ebook; 4 | import com.xiaolang.wiki.domain.EbookExample; 5 | import java.util.List; 6 | import org.apache.ibatis.annotations.Param; 7 | 8 | public interface EbookMapper { 9 | long countByExample(EbookExample example); 10 | 11 | int deleteByExample(EbookExample example); 12 | 13 | int deleteByPrimaryKey(Long id); 14 | 15 | int insert(Ebook record); 16 | 17 | int insertSelective(Ebook record); 18 | 19 | List selectByExample(EbookExample example); 20 | 21 | Ebook selectByPrimaryKey(Long id); 22 | 23 | int updateByExampleSelective(@Param("record") Ebook record, @Param("example") EbookExample example); 24 | 25 | int updateByExample(@Param("record") Ebook record, @Param("example") EbookExample example); 26 | 27 | int updateByPrimaryKeySelective(Ebook record); 28 | 29 | int updateByPrimaryKey(Ebook record); 30 | } -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/mapper/CategoryMapper.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.mapper; 2 | 3 | import com.xiaolang.wiki.domain.Category; 4 | import com.xiaolang.wiki.domain.CategoryExample; 5 | import java.util.List; 6 | import org.apache.ibatis.annotations.Param; 7 | 8 | public interface CategoryMapper { 9 | long countByExample(CategoryExample example); 10 | 11 | int deleteByExample(CategoryExample example); 12 | 13 | int deleteByPrimaryKey(Long id); 14 | 15 | int insert(Category record); 16 | 17 | int insertSelective(Category record); 18 | 19 | List selectByExample(CategoryExample example); 20 | 21 | Category selectByPrimaryKey(Long id); 22 | 23 | int updateByExampleSelective(@Param("record") Category record, @Param("example") CategoryExample example); 24 | 25 | int updateByExample(@Param("record") Category record, @Param("example") CategoryExample example); 26 | 27 | int updateByPrimaryKeySelective(Category record); 28 | 29 | int updateByPrimaryKey(Category record); 30 | } -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/req/EbookQueryReq.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.req; 2 | 3 | public class EbookQueryReq extends PageReq { 4 | private Long id; 5 | 6 | private String name; 7 | 8 | private Long categoryId2; 9 | 10 | public Long getId() { 11 | return id; 12 | } 13 | 14 | public void setId(Long id) { 15 | this.id = id; 16 | } 17 | 18 | public String getName() { 19 | return name; 20 | } 21 | 22 | public void setName(String name) { 23 | this.name = name; 24 | } 25 | 26 | public Long getCategoryId2() { 27 | return categoryId2; 28 | } 29 | 30 | public void setCategoryId2(Long categoryId2) { 31 | this.categoryId2 = categoryId2; 32 | } 33 | 34 | @Override 35 | public String toString() { 36 | return "EbookQueryReq{" + 37 | "id=" + id + 38 | ", name='" + name + '\'' + 39 | ", categoryId2=" + categoryId2 + 40 | "} " + super.toString(); 41 | } 42 | } -------------------------------------------------------------------------------- /web/src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 22 | 23 | 41 | 42 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/mapper/EbookSnapshotMapper.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.mapper; 2 | 3 | import com.xiaolang.wiki.domain.EbookSnapshot; 4 | import com.xiaolang.wiki.domain.EbookSnapshotExample; 5 | import java.util.List; 6 | import org.apache.ibatis.annotations.Param; 7 | 8 | public interface EbookSnapshotMapper { 9 | long countByExample(EbookSnapshotExample example); 10 | 11 | int deleteByExample(EbookSnapshotExample example); 12 | 13 | int deleteByPrimaryKey(Long id); 14 | 15 | int insert(EbookSnapshot record); 16 | 17 | int insertSelective(EbookSnapshot record); 18 | 19 | List selectByExample(EbookSnapshotExample example); 20 | 21 | EbookSnapshot selectByPrimaryKey(Long id); 22 | 23 | int updateByExampleSelective(@Param("record") EbookSnapshot record, @Param("example") EbookSnapshotExample example); 24 | 25 | int updateByExample(@Param("record") EbookSnapshot record, @Param("example") EbookSnapshotExample example); 26 | 27 | int updateByPrimaryKeySelective(EbookSnapshot record); 28 | 29 | int updateByPrimaryKey(EbookSnapshot record); 30 | } -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/util/RedisUtil.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.util; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.data.redis.core.RedisTemplate; 6 | import org.springframework.stereotype.Component; 7 | 8 | import javax.annotation.Resource; 9 | import java.util.concurrent.TimeUnit; 10 | 11 | //@User:hhb 12 | @Component 13 | public class RedisUtil { 14 | private static final Logger LOG = LoggerFactory.getLogger(RedisUtil.class); 15 | 16 | @Resource 17 | private RedisTemplate redisTemplate; 18 | 19 | /** 20 | * true: 不存在,放一个key 21 | * false: 已存在 22 | * @param key 23 | * @param second 24 | * @return 25 | */ 26 | public boolean validateRepeat(String key, long second) { 27 | if (redisTemplate.hasKey(key)) { 28 | LOG.info("key已存在: {}",key); 29 | return false; 30 | } else { 31 | LOG.info("key不存在, 放入: {}, 过期 {} 秒",key, second); 32 | redisTemplate.opsForValue().set(key, key, second, TimeUnit.SECONDS); 33 | return true; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/job/DocJob.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.job; 2 | 3 | import com.xiaolang.wiki.service.DocService; 4 | import com.xiaolang.wiki.util.SnowFlake; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.slf4j.MDC; 8 | import org.springframework.scheduling.annotation.Scheduled; 9 | import org.springframework.stereotype.Component; 10 | 11 | import javax.annotation.Resource; 12 | 13 | @Component 14 | public class DocJob { 15 | 16 | private static final Logger LOG = LoggerFactory.getLogger(DocJob.class); 17 | 18 | @Resource 19 | private SnowFlake snowFlake; 20 | 21 | @Resource 22 | private DocService docService; 23 | 24 | /** 25 | * 每过半分钟更新电子书信息 26 | */ 27 | @Scheduled(cron = "3/9 * * * * ? ") 28 | public void cron() { 29 | // 增加日志流水号码 30 | MDC.put("LOG_ID", String.valueOf(snowFlake.nextId())); 31 | 32 | LOG.info("开始更新电子书下的文档数据"); 33 | long start = System.currentTimeMillis(); 34 | docService.updateEbookInfo(); 35 | LOG.info("文档数据更新结束, 耗时: {}毫秒",System.currentTimeMillis() - start); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/rocketmq/VoteTopicConsumer.java: -------------------------------------------------------------------------------- 1 | //package com.xiaolang.wiki.rocketmq; 2 | // 3 | //import com.xiaolang.wiki.websocket.WebSocketServer; 4 | //import org.apache.rocketmq.common.message.MessageExt; 5 | //import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; 6 | //import org.apache.rocketmq.spring.core.RocketMQListener; 7 | //import org.slf4j.Logger; 8 | //import org.slf4j.LoggerFactory; 9 | //import org.springframework.stereotype.Service; 10 | // 11 | //import javax.annotation.Resource; 12 | // 13 | // @Service 14 | // @RocketMQMessageListener(consumerGroup = "default", topic = "VOTE_TOPIC") 15 | // public class VoteTopicConsumer implements RocketMQListener { 16 | // 17 | // private static final Logger LOG = LoggerFactory.getLogger(VoteTopicConsumer.class); 18 | // 19 | // @Resource 20 | // public WebSocketServer webSocketServer; 21 | // 22 | // @Override 23 | // public void onMessage(MessageExt messageExt) { 24 | // byte[] body = messageExt.getBody(); 25 | // LOG.info("ROCKETMQ收到消息:{}", new String(body)); 26 | // webSocketServer.sendInfo(new String(body)); 27 | // } 28 | // } 29 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/req/UserResetPasswordReq.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.req; 2 | 3 | import javax.validation.constraints.NotNull; 4 | import javax.validation.constraints.Pattern; 5 | 6 | public class UserResetPasswordReq { 7 | private Long id; 8 | 9 | @NotNull(message = "【密码】不能为空") 10 | @Pattern(regexp = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,32}$", message = "【密码】至少包含 数字和英文,长度6-32") 11 | private String password; 12 | 13 | 14 | public Long getId() { 15 | return id; 16 | } 17 | 18 | public void setId(Long id) { 19 | this.id = id; 20 | } 21 | 22 | public String getPassword() { 23 | return password; 24 | } 25 | 26 | public void setPassword(String password) { 27 | this.password = password; 28 | } 29 | 30 | @Override 31 | public String toString() { 32 | StringBuilder sb = new StringBuilder(); 33 | sb.append(getClass().getSimpleName()); 34 | sb.append(" ["); 35 | sb.append("Hash = ").append(hashCode()); 36 | sb.append(", id=").append(id); 37 | sb.append(", password=").append(password); 38 | sb.append("]"); 39 | return sb.toString(); 40 | } 41 | } -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/config/WikiApplication.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.config; 2 | 3 | import org.mybatis.spring.annotation.MapperScan; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | import org.springframework.context.annotation.ComponentScan; 9 | import org.springframework.core.env.Environment; 10 | import org.springframework.scheduling.annotation.EnableAsync; 11 | import org.springframework.scheduling.annotation.EnableScheduling; 12 | 13 | @ComponentScan("com.xiaolang") 14 | @MapperScan("com.xiaolang.wiki.mapper") 15 | @SpringBootApplication 16 | @EnableScheduling 17 | @EnableAsync 18 | public class WikiApplication { 19 | 20 | private static final Logger LOG = LoggerFactory.getLogger(WikiApplication.class); 21 | 22 | public static void main(String[] args) { 23 | 24 | SpringApplication app = new SpringApplication(WikiApplication.class); 25 | Environment env = app.run(args).getEnvironment(); 26 | LOG.info("启动成功"); 27 | LOG.info("地址:\thttp://127.0.0.1:{}",env.getProperty("server.port")); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/util/CopyUtil.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.util; 2 | 3 | import org.springframework.beans.BeanUtils; 4 | import org.springframework.util.CollectionUtils; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | /** 9 | @User hhb 10 | */ 11 | public class CopyUtil { 12 | 13 | /** 14 | * 单体复制 15 | */ 16 | public static T copy(Object source, Class clazz) { 17 | if (source == null) { 18 | return null; 19 | } 20 | T obj = null; 21 | try { 22 | obj = clazz.newInstance(); 23 | } catch (Exception e) { 24 | e.printStackTrace(); 25 | return null; 26 | } 27 | BeanUtils.copyProperties(source, obj); 28 | return obj; 29 | } 30 | 31 | /** 32 | * 列表复制 33 | */ 34 | public static List copyList(List source, Class clazz) { 35 | List target = new ArrayList<>(); 36 | if (!CollectionUtils.isEmpty(source)){ 37 | for (Object c: source) { 38 | T obj = copy(c, clazz); 39 | target.add(obj); 40 | } 41 | } 42 | return target; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/mapper/ContentMapper.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.mapper; 2 | 3 | import com.xiaolang.wiki.domain.Content; 4 | import com.xiaolang.wiki.domain.ContentExample; 5 | import java.util.List; 6 | import org.apache.ibatis.annotations.Param; 7 | 8 | public interface ContentMapper { 9 | long countByExample(ContentExample example); 10 | 11 | int deleteByExample(ContentExample example); 12 | 13 | int deleteByPrimaryKey(Long id); 14 | 15 | int insert(Content record); 16 | 17 | int insertSelective(Content record); 18 | 19 | List selectByExampleWithBLOBs(ContentExample example); 20 | 21 | List selectByExample(ContentExample example); 22 | 23 | Content selectByPrimaryKey(Long id); 24 | 25 | int updateByExampleSelective(@Param("record") Content record, @Param("example") ContentExample example); 26 | 27 | int updateByExampleWithBLOBs(@Param("record") Content record, @Param("example") ContentExample example); 28 | 29 | int updateByExample(@Param("record") Content record, @Param("example") ContentExample example); 30 | 31 | int updateByPrimaryKeySelective(Content record); 32 | 33 | int updateByPrimaryKeyWithBLOBs(Content record); 34 | } -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/config/SpringMvcConfig.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.config; 2 | 3 | import com.xiaolang.wiki.interceptor.LoginInterceptor; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 6 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 7 | 8 | import javax.annotation.Resource; 9 | 10 | @Configuration 11 | public class SpringMvcConfig implements WebMvcConfigurer { 12 | 13 | @Resource 14 | LoginInterceptor loginInterceptor; 15 | 16 | public void addInterceptors(InterceptorRegistry registry) { 17 | registry.addInterceptor(loginInterceptor) 18 | .addPathPatterns("/**") 19 | .excludePathPatterns( 20 | "/test/**", 21 | "/redis/**", 22 | "/user/login", 23 | "/category/all", 24 | "/ebook/list", 25 | "/doc/all/**", 26 | "/doc/vote/**", 27 | "/doc/find-content/**", 28 | "/ebook-snapshot/**" 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/req/UserLoginReq.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.req; 2 | 3 | import javax.validation.constraints.NotEmpty; 4 | 5 | public class UserLoginReq { 6 | @NotEmpty(message = "【用户名】不能为空") 7 | private String loginName; 8 | 9 | @NotEmpty(message = "【密码】不能为空") 10 | // @Pattern(regexp = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,32}$", message = "【密码】规则不正确") 11 | private String password; 12 | 13 | public String getLoginName() { 14 | return loginName; 15 | } 16 | 17 | public void setLoginName(String loginName) { 18 | this.loginName = loginName; 19 | } 20 | 21 | public String getPassword() { 22 | return password; 23 | } 24 | 25 | public void setPassword(String password) { 26 | this.password = password; 27 | } 28 | 29 | @Override 30 | public String toString() { 31 | StringBuilder sb = new StringBuilder(); 32 | sb.append(getClass().getSimpleName()); 33 | sb.append(" ["); 34 | sb.append("Hash = ").append(hashCode()); 35 | sb.append(", loginName=").append(loginName); 36 | sb.append(", password=").append(password); 37 | sb.append("]"); 38 | return sb.toString(); 39 | } 40 | } -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/job/EbookSnapshotJob.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.job; 2 | 3 | import com.xiaolang.wiki.service.EbookSnapshotService; 4 | import com.xiaolang.wiki.util.SnowFlake; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.slf4j.MDC; 8 | import org.springframework.scheduling.annotation.Scheduled; 9 | import org.springframework.stereotype.Component; 10 | 11 | import javax.annotation.Resource; 12 | 13 | @Component 14 | public class EbookSnapshotJob { 15 | 16 | private static final Logger LOG = LoggerFactory.getLogger(EbookSnapshotJob.class); 17 | 18 | @Resource 19 | private EbookSnapshotService ebookSnapshotService; 20 | 21 | @Resource 22 | private SnowFlake snowFlake; 23 | 24 | /** 25 | * 自定义cron表达式跑批 26 | * 只有等上一次执行完成,下一次才会在下一个时间点执行,错过就错过 27 | */ 28 | @Scheduled(cron = "6/9 * * * * ? ") 29 | public void doSnapshot() { 30 | // 增加日志流水号 31 | MDC.put("LOG_ID", String.valueOf(snowFlake.nextId())); 32 | LOG.info("生成今日电子书快照开始"); 33 | Long start = System.currentTimeMillis(); 34 | ebookSnapshotService.genSnapshot(); 35 | LOG.info("生成今日电子书快照结束,耗时:{}毫秒", System.currentTimeMillis() - start); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/resp/UserLoginResp.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.resp; 2 | 3 | public class UserLoginResp { 4 | private Long id; 5 | 6 | private String loginName; 7 | 8 | private String name; 9 | 10 | private String token; 11 | 12 | public Long getId() { 13 | return id; 14 | } 15 | 16 | public void setId(Long id) { 17 | this.id = id; 18 | } 19 | 20 | public String getLoginName() { 21 | return loginName; 22 | } 23 | 24 | public void setLoginName(String loginName) { 25 | this.loginName = loginName; 26 | } 27 | 28 | public String getName() { 29 | return name; 30 | } 31 | 32 | public void setName(String name) { 33 | this.name = name; 34 | } 35 | 36 | public String getToken() { 37 | return token; 38 | } 39 | 40 | public void setToken(String token) { 41 | this.token = token; 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | return "UserLoginResp{" + 47 | "id=" + id + 48 | ", loginName='" + loginName + '\'' + 49 | ", name='" + name + '\'' + 50 | ", token='" + token + '\'' + 51 | '}'; 52 | } 53 | } -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/config/JacksonConfig.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.config; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.fasterxml.jackson.databind.module.SimpleModule; 5 | import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.context.annotation.Primary; 10 | import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; 11 | 12 | @Configuration 13 | public class JacksonConfig { 14 | 15 | @Bean 16 | @Primary 17 | @ConditionalOnMissingBean(ObjectMapper.class) 18 | public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) 19 | { 20 | ObjectMapper objectMapper = builder.createXmlMapper(false).build(); 21 | 22 | // 全局配置序列化返回 JSON 处理 23 | SimpleModule simpleModule = new SimpleModule(); 24 | //JSON Long ==> String 25 | simpleModule.addSerializer(Long.class, ToStringSerializer.instance); 26 | objectMapper.registerModule(simpleModule); 27 | return objectMapper; 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/resp/CommonResp.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.resp; 2 | 3 | public class CommonResp { 4 | 5 | /** 6 | * 业务上的成功或失败 7 | */ 8 | private boolean success = true; 9 | 10 | /** 11 | * 返回信息 12 | */ 13 | private String message; 14 | 15 | /** 16 | * 返回泛型数据,自定义类型 17 | */ 18 | private T content; 19 | 20 | public boolean getSuccess() { 21 | return success; 22 | } 23 | 24 | public void setSuccess(boolean success) { 25 | this.success = success; 26 | } 27 | 28 | public String getMessage() { 29 | return message; 30 | } 31 | 32 | public void setMessage(String message) { 33 | this.message = message; 34 | } 35 | 36 | public T getContent() { 37 | return content; 38 | } 39 | 40 | public void setContent(T content) { 41 | this.content = content; 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | final StringBuffer sb = new StringBuffer("ResponseDto{"); 47 | sb.append("success=").append(success); 48 | sb.append(", message='").append(message).append('\''); 49 | sb.append(", content=").append(content); 50 | sb.append('}'); 51 | return sb.toString(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve-dev": "vue-cli-service serve --mode dev --port 8082", 7 | "serve-prod": "vue-cli-service serve --mode prod --port 8082", 8 | "build-dev": "vue-cli-service build --mode dev --port 8082", 9 | "build-prod": "vue-cli-service build --mode prod --port 8082", 10 | "lint": "vue-cli-service lint" 11 | }, 12 | "dependencies": { 13 | "@ant-design/icons-vue": "^6.0.1", 14 | "ant-design-vue": "^2.1.0", 15 | "axios": "^0.21.0", 16 | "highlight.js": "^11.0.1", 17 | "less": "^2.7.3", 18 | "less-loader": "^6.0.0", 19 | "vue": "^3.0.0", 20 | "vue-router": "^4.0.0-0", 21 | "vuex": "^4.0.0-0", 22 | "wangeditor": "^4.6.14" 23 | }, 24 | "devDependencies": { 25 | "@typescript-eslint/eslint-plugin": "^4.18.0", 26 | "@typescript-eslint/parser": "^4.18.0", 27 | "@vue/cli-plugin-eslint": "~4.5.0", 28 | "@vue/cli-plugin-router": "~4.5.0", 29 | "@vue/cli-plugin-typescript": "~4.5.0", 30 | "@vue/cli-plugin-vuex": "~4.5.0", 31 | "@vue/cli-service": "~4.5.0", 32 | "@vue/compiler-sfc": "^3.0.0", 33 | "@vue/eslint-config-typescript": "^7.0.0", 34 | "eslint": "^6.7.2", 35 | "eslint-plugin-vue": "^7.0.0", 36 | "typescript": "~4.1.5" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/job/TestJob.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.job;// package com.jiawa.wiki.job; 2 | // 3 | // import org.slf4j.Logger; 4 | // import org.slf4j.LoggerFactory; 5 | // import org.springframework.scheduling.annotation.Scheduled; 6 | // import org.springframework.stereotype.Component; 7 | // 8 | // import java.text.SimpleDateFormat; 9 | // import java.util.Date; 10 | // 11 | // @Component 12 | // public class TestJob { 13 | // 14 | // private static final Logger LOG = LoggerFactory.getLogger(TestJob.class); 15 | // 16 | // /** 17 | // * 固定时间间隔,fixedRate单位毫秒 18 | // */ 19 | // @Scheduled(fixedRate = 1000) 20 | // public void simple() throws InterruptedException { 21 | // SimpleDateFormat formatter = new SimpleDateFormat("mm:ss"); 22 | // String dateString = formatter.format(new Date()); 23 | // Thread.sleep(2000); 24 | // LOG.info("每隔5秒钟执行一次: {}", dateString); 25 | // } 26 | // 27 | // /** 28 | // * 自定义cron表达式跑批 29 | // * 只有等上一次执行完成,下一次才会在下一个时间点执行,错过就错过 30 | // */ 31 | // @Scheduled(cron = "*/1 * * * * ?") 32 | // public void cron() throws InterruptedException { 33 | // SimpleDateFormat formatter = new SimpleDateFormat("mm:ss SSS"); 34 | // String dateString = formatter.format(new Date()); 35 | // Thread.sleep(1500); 36 | // LOG.info("每隔1秒钟执行一次: {}", dateString); 37 | // } 38 | // 39 | // } 40 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/controller/EbookSnapshotController.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.controller; 2 | 3 | import com.xiaolang.wiki.resp.CommonResp; 4 | import com.xiaolang.wiki.resp.StatisticResp; 5 | import com.xiaolang.wiki.service.EbookSnapshotService; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | import javax.annotation.Resource; 11 | import java.util.List; 12 | 13 | @RestController 14 | @RequestMapping("/ebook-snapshot") 15 | public class EbookSnapshotController { 16 | 17 | @Resource 18 | private EbookSnapshotService ebookSnapshotService; 19 | 20 | @GetMapping("/get-statistic") 21 | public CommonResp getStatistic() { 22 | List statisticResp = ebookSnapshotService.getStatistic(); 23 | CommonResp> commonResp = new CommonResp<>(); 24 | commonResp.setContent(statisticResp); 25 | return commonResp; 26 | } 27 | 28 | @GetMapping("/get-30-statistic") 29 | public CommonResp get30Statistic() { 30 | List statisticResps = ebookSnapshotService.get30Statistic(); 31 | CommonResp> commonResp = new CommonResp<>(); 32 | commonResp.setContent(statisticResps); 33 | return commonResp; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/domain/Category.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.domain; 2 | 3 | public class Category { 4 | private Long id; 5 | 6 | private Long parent; 7 | 8 | private String name; 9 | 10 | private Integer sort; 11 | 12 | public Long getId() { 13 | return id; 14 | } 15 | 16 | public void setId(Long id) { 17 | this.id = id; 18 | } 19 | 20 | public Long getParent() { 21 | return parent; 22 | } 23 | 24 | public void setParent(Long parent) { 25 | this.parent = parent; 26 | } 27 | 28 | public String getName() { 29 | return name; 30 | } 31 | 32 | public void setName(String name) { 33 | this.name = name; 34 | } 35 | 36 | public Integer getSort() { 37 | return sort; 38 | } 39 | 40 | public void setSort(Integer sort) { 41 | this.sort = sort; 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | StringBuilder sb = new StringBuilder(); 47 | sb.append(getClass().getSimpleName()); 48 | sb.append(" ["); 49 | sb.append("Hash = ").append(hashCode()); 50 | sb.append(", id=").append(id); 51 | sb.append(", parent=").append(parent); 52 | sb.append(", name=").append(name); 53 | sb.append(", sort=").append(sort); 54 | sb.append("]"); 55 | return sb.toString(); 56 | } 57 | } -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/resp/CategoryQueryResp.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.resp; 2 | 3 | public class CategoryQueryResp { 4 | private Long id; 5 | 6 | private Long parent; 7 | 8 | private String name; 9 | 10 | private Integer sort; 11 | 12 | public Long getId() { 13 | return id; 14 | } 15 | 16 | public void setId(Long id) { 17 | this.id = id; 18 | } 19 | 20 | public Long getParent() { 21 | return parent; 22 | } 23 | 24 | public void setParent(Long parent) { 25 | this.parent = parent; 26 | } 27 | 28 | public String getName() { 29 | return name; 30 | } 31 | 32 | public void setName(String name) { 33 | this.name = name; 34 | } 35 | 36 | public Integer getSort() { 37 | return sort; 38 | } 39 | 40 | public void setSort(Integer sort) { 41 | this.sort = sort; 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | StringBuilder sb = new StringBuilder(); 47 | sb.append(getClass().getSimpleName()); 48 | sb.append(" ["); 49 | sb.append("Hash = ").append(hashCode()); 50 | sb.append(", id=").append(id); 51 | sb.append(", parent=").append(parent); 52 | sb.append(", name=").append(name); 53 | sb.append(", sort=").append(sort); 54 | sb.append("]"); 55 | return sb.toString(); 56 | } 57 | } -------------------------------------------------------------------------------- /web/src/components/the-footer.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | -------------------------------------------------------------------------------- /web/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import store from './store' 5 | import Antd from 'ant-design-vue'; 6 | // import 'ant-design-vue/dist/antd.css'; 7 | // import 'ant-design-vue/dist/antd.less'; 8 | import * as Icons from '@ant-design/icons-vue'; 9 | import axios from 'axios'; 10 | import {Tool} from "@/util/tool"; 11 | import '../public/reset.less' 12 | 13 | 14 | axios.defaults.baseURL = process.env.VUE_APP_SERVER; 15 | 16 | /** 17 | * axios拦截器 18 | */ 19 | axios.interceptors.request.use(function (config) { 20 | console.log('请求参数: ',config); 21 | const token = store.state.user.token; 22 | if (Tool.isNotEmpty(token)) { 23 | config.headers.token = token; 24 | console.log("请求头headers增加token: ", token); 25 | } 26 | return config; 27 | },error => { 28 | return Promise.reject(error); 29 | }); 30 | axios.interceptors.response.use(function (response) { 31 | console.log('返回结果: ',response); 32 | return response; 33 | },error => { 34 | console.log('返回错误: ',error); 35 | return Promise.reject(error); 36 | }); 37 | 38 | const app = createApp(App); 39 | app.use(store).use(router).use(Antd).mount('#app'); 40 | 41 | //全局使用图标 42 | const icons: any = Icons; 43 | for (const i in icons){ 44 | app.component(i, icons[i]); 45 | } 46 | 47 | console.log('环境: ', process.env.NODE_ENV); 48 | console.log('服务端: ', process.env.VUE_APP_SERVER); -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/filter/LogFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | package com.xiaolang.wiki.filter; 3 | 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.stereotype.Component; 7 | 8 | import javax.servlet.*; 9 | import javax.servlet.http.HttpServletRequest; 10 | import java.io.IOException; 11 | 12 | @Component 13 | public class LogFilter implements Filter { 14 | 15 | private static final Logger LOG = LoggerFactory.getLogger(LogFilter.class); 16 | 17 | @Override 18 | public void init(FilterConfig filterConfig) throws ServletException{ 19 | 20 | } 21 | 22 | @Override 23 | public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { 24 | // 打印请求消息 25 | HttpServletRequest request = (HttpServletRequest) servletRequest; 26 | LOG.info("---------------LogFilter 开始---------------"); 27 | LOG.info("请求地址: {} {}",request.getRequestURL().toString(),request.getMethod()); 28 | LOG.info("远程地址: {}",request.getRemoteAddr()); 29 | 30 | long startTime = System.currentTimeMillis(); 31 | filterChain.doFilter(servletRequest,servletResponse); 32 | LOG.info("---------------LogFilter 结束 耗时: {} ms---------------",System.currentTimeMillis() - startTime); 33 | 34 | } 35 | 36 | @Override 37 | public void destroy() { 38 | 39 | } 40 | 41 | 42 | } 43 | */ 44 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/controller/EbookController.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.controller; 2 | 3 | import com.xiaolang.wiki.req.EbookQueryReq; 4 | import com.xiaolang.wiki.req.EbookSaveReq; 5 | import com.xiaolang.wiki.resp.CommonResp; 6 | import com.xiaolang.wiki.resp.EbookQueryResp; 7 | import com.xiaolang.wiki.resp.PageResp; 8 | import com.xiaolang.wiki.service.EbookService; 9 | import org.springframework.web.bind.annotation.*; 10 | 11 | import javax.annotation.Resource; 12 | import javax.validation.Valid; 13 | 14 | @RestController 15 | @RequestMapping("/ebook") 16 | public class EbookController { 17 | 18 | @Resource 19 | private EbookService ebookService; 20 | 21 | @GetMapping("/list") 22 | public CommonResp list(@Valid EbookQueryReq req){ 23 | CommonResp> resp = new CommonResp<>(); 24 | PageResp list = ebookService.list(req); 25 | resp.setContent(list); 26 | return resp; 27 | } 28 | 29 | @PostMapping("/save") 30 | public CommonResp save(@Valid @RequestBody EbookSaveReq req){ 31 | CommonResp resp = new CommonResp(); 32 | ebookService.save(req); 33 | return resp; 34 | } 35 | 36 | 37 | // 删除 38 | @DeleteMapping("/delete/{id}") 39 | public CommonResp delete(@PathVariable Long id){ 40 | CommonResp resp = new CommonResp<>(); 41 | ebookService.delete(id); 42 | return resp; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/domain/User.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.domain; 2 | 3 | public class User { 4 | private Long id; 5 | 6 | private String loginName; 7 | 8 | private String name; 9 | 10 | private String password; 11 | 12 | public Long getId() { 13 | return id; 14 | } 15 | 16 | public void setId(Long id) { 17 | this.id = id; 18 | } 19 | 20 | public String getLoginName() { 21 | return loginName; 22 | } 23 | 24 | public void setLoginName(String loginName) { 25 | this.loginName = loginName; 26 | } 27 | 28 | public String getName() { 29 | return name; 30 | } 31 | 32 | public void setName(String name) { 33 | this.name = name; 34 | } 35 | 36 | public String getPassword() { 37 | return password; 38 | } 39 | 40 | public void setPassword(String password) { 41 | this.password = password; 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | StringBuilder sb = new StringBuilder(); 47 | sb.append(getClass().getSimpleName()); 48 | sb.append(" ["); 49 | sb.append("Hash = ").append(hashCode()); 50 | sb.append(", id=").append(id); 51 | sb.append(", loginName=").append(loginName); 52 | sb.append(", name=").append(name); 53 | sb.append(", password=").append(password); 54 | sb.append("]"); 55 | return sb.toString(); 56 | } 57 | } -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/resp/UserQueryResp.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.resp; 2 | 3 | public class UserQueryResp { 4 | private Long id; 5 | 6 | private String loginName; 7 | 8 | private String name; 9 | 10 | private String password; 11 | 12 | public Long getId() { 13 | return id; 14 | } 15 | 16 | public void setId(Long id) { 17 | this.id = id; 18 | } 19 | 20 | public String getLoginName() { 21 | return loginName; 22 | } 23 | 24 | public void setLoginName(String loginName) { 25 | this.loginName = loginName; 26 | } 27 | 28 | public String getName() { 29 | return name; 30 | } 31 | 32 | public void setName(String name) { 33 | this.name = name; 34 | } 35 | 36 | public String getPassword() { 37 | return password; 38 | } 39 | 40 | public void setPassword(String password) { 41 | this.password = password; 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | StringBuilder sb = new StringBuilder(); 47 | sb.append(getClass().getSimpleName()); 48 | sb.append(" ["); 49 | sb.append("Hash = ").append(hashCode()); 50 | sb.append(", id=").append(id); 51 | sb.append(", loginName=").append(loginName); 52 | sb.append(", name=").append(name); 53 | sb.append(", password=").append(password); 54 | sb.append("]"); 55 | return sb.toString(); 56 | } 57 | } -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/req/CategorySaveReq.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.req; 2 | 3 | import javax.validation.constraints.NotNull; 4 | 5 | public class CategorySaveReq { 6 | private Long id; 7 | 8 | private Long parent; 9 | 10 | @NotNull(message = "【名称】不能为空") 11 | private String name; 12 | 13 | @NotNull(message = "【排序】不能为空") 14 | private Integer sort; 15 | 16 | public Long getId() { 17 | return id; 18 | } 19 | 20 | public void setId(Long id) { 21 | this.id = id; 22 | } 23 | 24 | public Long getParent() { 25 | return parent; 26 | } 27 | 28 | public void setParent(Long parent) { 29 | this.parent = parent; 30 | } 31 | 32 | public String getName() { 33 | return name; 34 | } 35 | 36 | public void setName(String name) { 37 | this.name = name; 38 | } 39 | 40 | public Integer getSort() { 41 | return sort; 42 | } 43 | 44 | public void setSort(Integer sort) { 45 | this.sort = sort; 46 | } 47 | 48 | @Override 49 | public String toString() { 50 | StringBuilder sb = new StringBuilder(); 51 | sb.append(getClass().getSimpleName()); 52 | sb.append(" ["); 53 | sb.append("Hash = ").append(hashCode()); 54 | sb.append(", id=").append(id); 55 | sb.append(", parent=").append(parent); 56 | sb.append(", name=").append(name); 57 | sb.append(", sort=").append(sort); 58 | sb.append("]"); 59 | return sb.toString(); 60 | } 61 | } -------------------------------------------------------------------------------- /web/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 小浪wiki库 13 | 14 | 15 | 18 |
19 | 26 |
27 |
37 | 首次加载比较缓慢,请稍作等待... 38 |
39 |
40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/interceptor/LogInterceptor.java: -------------------------------------------------------------------------------- 1 | /* 2 | package com.xiaolang.wiki.interceptor; 3 | 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.stereotype.Component; 7 | import org.springframework.web.servlet.HandlerInterceptor; 8 | import org.springframework.web.servlet.ModelAndView; 9 | 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | 13 | */ 14 | /** 15 | * 拦截器:Spring框架特有的,常用于登录校验,权限校验,请求日志打印 16 | *//* 17 | 18 | @Component 19 | public class LogInterceptor implements HandlerInterceptor { 20 | 21 | private static final Logger LOG = LoggerFactory.getLogger(LogInterceptor.class); 22 | 23 | @Override 24 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{ 25 | //打印请求信息 26 | LOG.info("---------------LogInterceptor开始---------------"); 27 | LOG.info("请求地址: {} {}",request.getRequestURL().toString(),request.getMethod()); 28 | LOG.info("远程地址: {}",request.getRemoteAddr()); 29 | 30 | long startTime = System.currentTimeMillis(); 31 | request.setAttribute("requestStartTime",startTime); 32 | return true; 33 | } 34 | 35 | @Override 36 | public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, 37 | ModelAndView modelAndView) throws Exception{ 38 | long startTime = (Long)request.getAttribute("requestStartTime"); 39 | LOG.info("-----------------LogInterceptor 结束 耗时: {} ms-----------------",System.currentTimeMillis() - startTime); 40 | } 41 | } 42 | */ 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### SpringBoot + Vue3 前后端分离wiki知识库系统 2 | 3 | 使用 SpringBoot + Vue3 + AntDesignVue 进行开发的 wiki 知识库 4 | 跟着慕课课程进行学习 5 | 6 | 7 | 8 | ### 安装运行 9 | 10 | 后端需要工具:JDK1.8 IDEA Mysql Redis maven 11 | 12 | > 首先需要克隆该项目 13 | 14 | ```bash 15 | git clone https://gitee.com/lang-tian/wiki 16 | # or 17 | git clone https://github.com/LianTianNo1/wiki_vue3 18 | ``` 19 | 20 | 前端需要Node.js环境 21 | 22 | > 进入目录 23 | 24 | ```bash 25 | cd wiki 26 | # or 27 | cd wiki_vue3 28 | 29 | # 安装依赖 30 | npm install 31 | 32 | # 启动项目 33 | npm run serve-dev 34 | ``` 35 | 36 | > 完成的功能... 37 | 38 | (1) 首页。 使用Ant Design在左侧边栏展示列表,这样就可以直接一一展示课程分类,点击全部查看主要内容中的课程分类。 39 | 40 | (2) 用户查询。点击用户管理,跳转到页面,用户全部查询,并分页展示,在输入框中输入,点击查询,则实现模糊查询。 41 | 42 | (3) 用户增加。增加一名用户,连接后端添加入数据库。 43 | 44 | (4) 重置密码。用户可以修改自己的密码,通过MD5进行加密。 45 | 46 | (5) 用户编辑。编辑用户基本信息。 47 | 48 | (6) 用户删除。删除用户信息。 49 | 50 | (7) 内容查询。点击内容管理,将全部查询一次,在输入框中输入,点击查询,进行模糊查询。 51 | 52 | (8) 内容新增。使用拟态框展示内容基本信息,填入内容基本信息进行添加。 53 | 54 | (9) 文档管理。编写课程文档。 55 | 56 | (10) 内容编辑。编辑文档基本内容。 57 | 58 | (11) 内容删除。删除这个文档。 59 | 60 | (12)分类增加。新增一个课程。 61 | 62 | (13) 分类编辑。修改课程名,其父分类,修改排序。 63 | 64 | (14) 分类删除。删除一个课程。 65 | 66 | (15) 课程文档。查看课程文档,学习课程。 67 | 68 | (16) 关于我们。软件持有者的基本信息。 69 | 70 | (17) 退出系统。清空token,返回登录界面。 71 | 72 | 73 | 74 | ### 项目运行截图 75 | 76 | ![image-20220512113143008](https://s2.loli.net/2022/05/12/xaXZq3KTpGAb6nf.png) 77 | 78 | ![image-20220512113447505](https://s2.loli.net/2022/05/12/QAtSEIoDp2mPlMv.png) 79 | 80 | ![image-20220512113524408](https://s2.loli.net/2022/05/12/8TLR3gcHW5azrU4.png) 81 | 82 | ![image-20220512113629406](https://s2.loli.net/2022/05/12/XthZGnORL9S6v3i.png) 83 | 84 | ![image-20220512113703751](https://s2.loli.net/2022/05/12/anxbqdOokYHl1CJ.png) 85 | 86 | ![image-20220512113745011](https://s2.loli.net/2022/05/12/neSY7oszHLvXWjr.png) -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/req/UserSaveReq.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.req; 2 | 3 | import javax.validation.constraints.NotNull; 4 | import javax.validation.constraints.Pattern; 5 | 6 | public class UserSaveReq { 7 | private Long id; 8 | 9 | @NotNull(message = "【用户名】不能为空") 10 | private String loginName; 11 | 12 | @NotNull(message = "【昵称】不能为空") 13 | private String name; 14 | 15 | @NotNull(message = "【密码】不能为空") 16 | @Pattern(regexp = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,32}$", message = "【密码】至少包含 数字和英文,长度6-32") 17 | private String password; 18 | 19 | public Long getId() { 20 | return id; 21 | } 22 | 23 | public void setId(Long id) { 24 | this.id = id; 25 | } 26 | 27 | public String getLoginName() { 28 | return loginName; 29 | } 30 | 31 | public void setLoginName(String loginName) { 32 | this.loginName = loginName; 33 | } 34 | 35 | public String getName() { 36 | return name; 37 | } 38 | 39 | public void setName(String name) { 40 | this.name = name; 41 | } 42 | 43 | public String getPassword() { 44 | return password; 45 | } 46 | 47 | public void setPassword(String password) { 48 | this.password = password; 49 | } 50 | 51 | @Override 52 | public String toString() { 53 | StringBuilder sb = new StringBuilder(); 54 | sb.append(getClass().getSimpleName()); 55 | sb.append(" ["); 56 | sb.append("Hash = ").append(hashCode()); 57 | sb.append(", id=").append(id); 58 | sb.append(", loginName=").append(loginName); 59 | sb.append(", name=").append(name); 60 | sb.append(", password=").append(password); 61 | sb.append("]"); 62 | return sb.toString(); 63 | } 64 | } -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/controller/CategoryController.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.controller; 2 | 3 | import com.xiaolang.wiki.req.CategoryQueryReq; 4 | import com.xiaolang.wiki.req.CategorySaveReq; 5 | import com.xiaolang.wiki.resp.CommonResp; 6 | import com.xiaolang.wiki.resp.CategoryQueryResp; 7 | import com.xiaolang.wiki.resp.PageResp; 8 | import com.xiaolang.wiki.service.CategoryService; 9 | import org.springframework.web.bind.annotation.*; 10 | 11 | import javax.annotation.Resource; 12 | import javax.validation.Valid; 13 | import java.util.List; 14 | 15 | @RestController 16 | @RequestMapping("/category") 17 | public class CategoryController { 18 | 19 | @Resource 20 | private CategoryService categoryService; 21 | 22 | @GetMapping("/all") 23 | public CommonResp all(){ 24 | CommonResp> resp = new CommonResp<>(); 25 | List list = categoryService.all(); 26 | resp.setContent(list); 27 | return resp; 28 | } 29 | @GetMapping("/list") 30 | public CommonResp list(@Valid CategoryQueryReq req){ 31 | CommonResp> resp = new CommonResp<>(); 32 | PageResp list = categoryService.list(req); 33 | resp.setContent(list); 34 | return resp; 35 | } 36 | 37 | @PostMapping("/save") 38 | public CommonResp save(@Valid @RequestBody CategorySaveReq req){ 39 | CommonResp resp = new CommonResp(); 40 | categoryService.save(req); 41 | return resp; 42 | } 43 | 44 | 45 | // 删除 46 | @DeleteMapping("/delete/{id}") 47 | public CommonResp delete(@PathVariable Long id){ 48 | CommonResp resp = new CommonResp<>(); 49 | categoryService.delete(id); 50 | return resp; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/resp/StatisticResp.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.resp; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | 5 | import java.util.Date; 6 | 7 | public class StatisticResp { 8 | 9 | @JsonFormat(pattern = "MM-dd", timezone = "GMT+8") 10 | private Date date; 11 | 12 | private Integer viewCount; 13 | 14 | private Integer voteCount; 15 | 16 | private Integer viewIncrease; 17 | 18 | private Integer voteIncrease; 19 | 20 | public Date getDate() { 21 | return date; 22 | } 23 | 24 | public void setDate(Date date) { 25 | this.date = date; 26 | } 27 | 28 | public Integer getViewCount() { 29 | return viewCount; 30 | } 31 | 32 | public void setViewCount(Integer viewCount) { 33 | this.viewCount = viewCount; 34 | } 35 | 36 | public Integer getVoteCount() { 37 | return voteCount; 38 | } 39 | 40 | public void setVoteCount(Integer voteCount) { 41 | this.voteCount = voteCount; 42 | } 43 | 44 | public Integer getViewIncrease() { 45 | return viewIncrease; 46 | } 47 | 48 | public void setViewIncrease(Integer viewIncrease) { 49 | this.viewIncrease = viewIncrease; 50 | } 51 | 52 | public Integer getVoteIncrease() { 53 | return voteIncrease; 54 | } 55 | 56 | public void setVoteIncrease(Integer voteIncrease) { 57 | this.voteIncrease = voteIncrease; 58 | } 59 | 60 | @Override 61 | public String toString() { 62 | return "StatisticResp{" + 63 | "date=" + date + 64 | ", viewCount=" + viewCount + 65 | ", voteCount=" + voteCount + 66 | ", viewIncrease=" + viewIncrease + 67 | ", voteIncrease=" + voteIncrease + 68 | '}'; 69 | } 70 | } -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/websocket/WebSocketServer.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.websocket; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.stereotype.Component; 6 | 7 | import javax.websocket.*; 8 | import javax.websocket.server.PathParam; 9 | import javax.websocket.server.ServerEndpoint; 10 | import java.io.IOException; 11 | import java.util.HashMap; 12 | 13 | @Component 14 | @ServerEndpoint("/ws/{token}") 15 | public class WebSocketServer { 16 | private static final Logger LOG = LoggerFactory.getLogger(WebSocketServer.class); 17 | 18 | /** 19 | * 每个客户端一个token 20 | */ 21 | private String token = ""; 22 | 23 | private static HashMap map = new HashMap<>(); 24 | 25 | /** 26 | * 连接成功 27 | */ 28 | @OnOpen 29 | public void onOpen(Session session, @PathParam("token") String token) { 30 | map.put(token, session); 31 | this.token = token; 32 | LOG.info("有新连接:token:{},session id:{},当前连接数:{}", token, session.getId(), map.size()); 33 | } 34 | 35 | /** 36 | * 连接关闭 37 | */ 38 | @OnClose 39 | public void onClose(Session session) { 40 | map.remove(this.token); 41 | LOG.info("连接关闭,token:{},session id:{}!当前连接数:{}", this.token, session.getId(), map.size()); 42 | } 43 | 44 | /** 45 | * 收到消息 46 | */ 47 | @OnMessage 48 | public void onMessage(String message, Session session) { 49 | LOG.info("收到消息:{},内容:{}", token, message); 50 | } 51 | 52 | /** 53 | * 连接错误 54 | */ 55 | @OnError 56 | public void onError(Session session, Throwable error) { 57 | LOG.error("发生错误", error); 58 | } 59 | 60 | /** 61 | * 群发消息 62 | */ 63 | public void sendInfo(String message) { 64 | for (String token : map.keySet()) { 65 | Session session = map.get(token); 66 | try { 67 | session.getBasicRemote().sendText(message); 68 | } catch (IOException e) { 69 | LOG.error("推送消息失败:{},内容:{}", token, message); 70 | } 71 | LOG.info("推送消息:{},内容:{}", token, message); 72 | } 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/domain/Doc.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.domain; 2 | 3 | public class Doc { 4 | private Long id; 5 | 6 | private Long ebookId; 7 | 8 | private Long parent; 9 | 10 | private String name; 11 | 12 | private Integer sort; 13 | 14 | private Integer viewCount; 15 | 16 | private Integer voteCount; 17 | 18 | public Long getId() { 19 | return id; 20 | } 21 | 22 | public void setId(Long id) { 23 | this.id = id; 24 | } 25 | 26 | public Long getEbookId() { 27 | return ebookId; 28 | } 29 | 30 | public void setEbookId(Long ebookId) { 31 | this.ebookId = ebookId; 32 | } 33 | 34 | public Long getParent() { 35 | return parent; 36 | } 37 | 38 | public void setParent(Long parent) { 39 | this.parent = parent; 40 | } 41 | 42 | public String getName() { 43 | return name; 44 | } 45 | 46 | public void setName(String name) { 47 | this.name = name; 48 | } 49 | 50 | public Integer getSort() { 51 | return sort; 52 | } 53 | 54 | public void setSort(Integer sort) { 55 | this.sort = sort; 56 | } 57 | 58 | public Integer getViewCount() { 59 | return viewCount; 60 | } 61 | 62 | public void setViewCount(Integer viewCount) { 63 | this.viewCount = viewCount; 64 | } 65 | 66 | public Integer getVoteCount() { 67 | return voteCount; 68 | } 69 | 70 | public void setVoteCount(Integer voteCount) { 71 | this.voteCount = voteCount; 72 | } 73 | 74 | @Override 75 | public String toString() { 76 | StringBuilder sb = new StringBuilder(); 77 | sb.append(getClass().getSimpleName()); 78 | sb.append(" ["); 79 | sb.append("Hash = ").append(hashCode()); 80 | sb.append(", id=").append(id); 81 | sb.append(", ebookId=").append(ebookId); 82 | sb.append(", parent=").append(parent); 83 | sb.append(", name=").append(name); 84 | sb.append(", sort=").append(sort); 85 | sb.append(", viewCount=").append(viewCount); 86 | sb.append(", voteCount=").append(voteCount); 87 | sb.append("]"); 88 | return sb.toString(); 89 | } 90 | } -------------------------------------------------------------------------------- /src/main/resources/generator/generator-config.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 28 | 29 | 30 | 31 | 33 | 34 | 35 | 37 | 38 | 39 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/resp/DocQueryResp.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.resp; 2 | 3 | public class DocQueryResp { 4 | private Long id; 5 | 6 | private Long ebookId; 7 | 8 | private Long parent; 9 | 10 | private String name; 11 | 12 | private Integer sort; 13 | 14 | private Integer viewCount; 15 | 16 | private Integer voteCount; 17 | 18 | public Long getId() { 19 | return id; 20 | } 21 | 22 | public void setId(Long id) { 23 | this.id = id; 24 | } 25 | 26 | public Long getEbookId() { 27 | return ebookId; 28 | } 29 | 30 | public void setEbookId(Long ebookId) { 31 | this.ebookId = ebookId; 32 | } 33 | 34 | public Long getParent() { 35 | return parent; 36 | } 37 | 38 | public void setParent(Long parent) { 39 | this.parent = parent; 40 | } 41 | 42 | public String getName() { 43 | return name; 44 | } 45 | 46 | public void setName(String name) { 47 | this.name = name; 48 | } 49 | 50 | public Integer getSort() { 51 | return sort; 52 | } 53 | 54 | public void setSort(Integer sort) { 55 | this.sort = sort; 56 | } 57 | 58 | public Integer getViewCount() { 59 | return viewCount; 60 | } 61 | 62 | public void setViewCount(Integer viewCount) { 63 | this.viewCount = viewCount; 64 | } 65 | 66 | public Integer getVoteCount() { 67 | return voteCount; 68 | } 69 | 70 | public void setVoteCount(Integer voteCount) { 71 | this.voteCount = voteCount; 72 | } 73 | 74 | @Override 75 | public String toString() { 76 | StringBuilder sb = new StringBuilder(); 77 | sb.append(getClass().getSimpleName()); 78 | sb.append(" ["); 79 | sb.append("Hash = ").append(hashCode()); 80 | sb.append(", id=").append(id); 81 | sb.append(", ebookId=").append(ebookId); 82 | sb.append(", parent=").append(parent); 83 | sb.append(", name=").append(name); 84 | sb.append(", sort=").append(sort); 85 | sb.append(", viewCount=").append(viewCount); 86 | sb.append(", voteCount=").append(voteCount); 87 | sb.append("]"); 88 | return sb.toString(); 89 | } 90 | } -------------------------------------------------------------------------------- /web/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router' 2 | import Home from '../views/home.vue' 3 | import About from '../views/about.vue' 4 | import Doc from '../views/doc.vue' 5 | import AdminUser from '../views/admin/admin-user.vue' 6 | import AdminEbook from '../views/admin/admin-ebook.vue' 7 | import AdminCategory from '../views/admin/admin-category.vue' 8 | import AdminDoc from '../views/admin/admin-doc.vue' 9 | import store from "@/store"; 10 | import {Tool} from "@/util/tool"; 11 | 12 | 13 | const routes: Array = [ 14 | { 15 | path: '/', 16 | name: 'Home', 17 | component: Home 18 | }, 19 | { 20 | path: '/about', 21 | name: 'About', 22 | component: About 23 | // route level code-splitting 24 | // this generates a separate chunk (about.[hash].js) for this route 25 | // which is lazy-loaded when the route is visited. 26 | // component: () => import(/* webpackChunkName: "about" */ '../views/about.vue') 27 | }, 28 | { 29 | path: '/admin/user', 30 | name: 'AdminUser', 31 | component: AdminUser, 32 | meta: { 33 | loginRequire:true 34 | } 35 | }, 36 | { 37 | path: '/admin/ebook', 38 | name: 'AdminEbook', 39 | component: AdminEbook, 40 | meta: { 41 | loginRequire:true 42 | } 43 | }, 44 | { 45 | path: '/admin/category', 46 | name: 'AdminCategory', 47 | component: AdminCategory, 48 | meta: { 49 | loginRequire:true 50 | } 51 | }, 52 | { 53 | path: '/admin/doc', 54 | name: 'AdminDoc', 55 | component: AdminDoc, 56 | meta: { 57 | loginRequire:true 58 | } 59 | }, 60 | { 61 | path: '/doc', 62 | name: 'Doc', 63 | component: Doc 64 | } 65 | ] 66 | 67 | const router = createRouter({ 68 | history: createWebHistory(process.env.BASE_URL), 69 | routes 70 | }); 71 | 72 | // 路由登录拦截 73 | router.beforeEach((to, from, next) => { 74 | //要不要对meta.loginRequire属性做拦截监控 75 | if (to.matched.some(function (item) { 76 | console.log(item, "是否需要登录校验: ", item.meta.loginRequire); 77 | return item.meta.loginRequire; 78 | })) { 79 | const loginUser = store.state.user; 80 | if (Tool.isEmpty(loginUser)) { 81 | console.log("用户未登录!"); 82 | next('/'); 83 | } else { 84 | next(); 85 | } 86 | } else { 87 | next(); 88 | } 89 | }); 90 | 91 | export default router 92 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/controller/DocController.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.controller; 2 | 3 | import com.xiaolang.wiki.req.DocQueryReq; 4 | import com.xiaolang.wiki.req.DocSaveReq; 5 | import com.xiaolang.wiki.resp.DocQueryResp; 6 | import com.xiaolang.wiki.resp.CommonResp; 7 | import com.xiaolang.wiki.resp.PageResp; 8 | import com.xiaolang.wiki.service.DocService; 9 | import org.springframework.web.bind.annotation.*; 10 | 11 | import javax.annotation.Resource; 12 | import javax.validation.Valid; 13 | import java.util.Arrays; 14 | import java.util.List; 15 | 16 | @RestController 17 | @RequestMapping("/doc") 18 | public class DocController { 19 | 20 | @Resource 21 | private DocService docService; 22 | 23 | @GetMapping("/all/{ebookId}") 24 | public CommonResp all(@PathVariable Long ebookId){ 25 | CommonResp> resp = new CommonResp<>(); 26 | List list = docService.all(ebookId); 27 | resp.setContent(list); 28 | return resp; 29 | } 30 | @GetMapping("/list") 31 | public CommonResp list(@Valid DocQueryReq req){ 32 | CommonResp> resp = new CommonResp<>(); 33 | PageResp list = docService.list(req); 34 | resp.setContent(list); 35 | return resp; 36 | } 37 | 38 | @PostMapping("/save") 39 | public CommonResp save(@Valid @RequestBody DocSaveReq req){ 40 | CommonResp resp = new CommonResp(); 41 | docService.save(req); 42 | return resp; 43 | } 44 | 45 | 46 | // 删除 47 | @DeleteMapping("/delete/{idsStr}") 48 | public CommonResp delete(@PathVariable String idsStr){ 49 | CommonResp resp = new CommonResp<>(); 50 | List list = Arrays.asList(idsStr.split(",")); 51 | docService.delete(list); 52 | return resp; 53 | } 54 | 55 | @GetMapping("/find-content/{id}") 56 | public CommonResp findContent(@PathVariable Long id){ 57 | CommonResp resp = new CommonResp<>(); 58 | String content = docService.findContent(id); 59 | System.out.println(content); 60 | resp.setContent(content); 61 | return resp; 62 | } 63 | 64 | @GetMapping("/vote/{id}") 65 | public CommonResp vote(@PathVariable Long id) { 66 | CommonResp commonResp = new CommonResp(); 67 | docService.vote(id); 68 | return commonResp; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/domain/EbookSnapshot.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.domain; 2 | 3 | import java.util.Date; 4 | 5 | public class EbookSnapshot { 6 | private Long id; 7 | 8 | private Long ebookId; 9 | 10 | private Date date; 11 | 12 | private Integer viewCount; 13 | 14 | private Integer voteCount; 15 | 16 | private Integer viewIncrease; 17 | 18 | private Integer voteIncrease; 19 | 20 | public Long getId() { 21 | return id; 22 | } 23 | 24 | public void setId(Long id) { 25 | this.id = id; 26 | } 27 | 28 | public Long getEbookId() { 29 | return ebookId; 30 | } 31 | 32 | public void setEbookId(Long ebookId) { 33 | this.ebookId = ebookId; 34 | } 35 | 36 | public Date getDate() { 37 | return date; 38 | } 39 | 40 | public void setDate(Date date) { 41 | this.date = date; 42 | } 43 | 44 | public Integer getViewCount() { 45 | return viewCount; 46 | } 47 | 48 | public void setViewCount(Integer viewCount) { 49 | this.viewCount = viewCount; 50 | } 51 | 52 | public Integer getVoteCount() { 53 | return voteCount; 54 | } 55 | 56 | public void setVoteCount(Integer voteCount) { 57 | this.voteCount = voteCount; 58 | } 59 | 60 | public Integer getViewIncrease() { 61 | return viewIncrease; 62 | } 63 | 64 | public void setViewIncrease(Integer viewIncrease) { 65 | this.viewIncrease = viewIncrease; 66 | } 67 | 68 | public Integer getVoteIncrease() { 69 | return voteIncrease; 70 | } 71 | 72 | public void setVoteIncrease(Integer voteIncrease) { 73 | this.voteIncrease = voteIncrease; 74 | } 75 | 76 | @Override 77 | public String toString() { 78 | StringBuilder sb = new StringBuilder(); 79 | sb.append(getClass().getSimpleName()); 80 | sb.append(" ["); 81 | sb.append("Hash = ").append(hashCode()); 82 | sb.append(", id=").append(id); 83 | sb.append(", ebookId=").append(ebookId); 84 | sb.append(", date=").append(date); 85 | sb.append(", viewCount=").append(viewCount); 86 | sb.append(", voteCount=").append(voteCount); 87 | sb.append(", viewIncrease=").append(viewIncrease); 88 | sb.append(", voteIncrease=").append(voteIncrease); 89 | sb.append("]"); 90 | return sb.toString(); 91 | } 92 | } -------------------------------------------------------------------------------- /web/src/util/tool.ts: -------------------------------------------------------------------------------- 1 | export class Tool { 2 | /** 3 | * 空校验 null或""都返回true 4 | */ 5 | public static isEmpty (obj: any) { 6 | if ((typeof obj === 'string')) { 7 | return !obj || obj.replace(/\s+/g, "") === "" 8 | } else { 9 | return (!obj || JSON.stringify(obj) === "{}" || obj.length === 0); 10 | } 11 | } 12 | 13 | /** 14 | * 非空校验 15 | */ 16 | public static isNotEmpty (obj: any) { 17 | return !this.isEmpty(obj); 18 | } 19 | 20 | /** 21 | * 对象复制 22 | * @param obj 23 | */ 24 | public static copy (obj: object) { 25 | if (Tool.isNotEmpty(obj)) { 26 | return JSON.parse(JSON.stringify(obj)); 27 | } 28 | } 29 | 30 | 31 | /* public static array2Tree(array: any, parentId: number){ 32 | if (Tool.isEmpty(array)){ 33 | return []; 34 | } 35 | const result = []; 36 | for (let i = 0; i < array.length; i++){ 37 | const c = array[i]; 38 | if (Number(c.parent) === Number(parentId)){ 39 | result.push(c); 40 | 41 | //递归查看当前节点对应的子节点 42 | const children = Tool.array2Tree(array,c.id); 43 | if (Tool.isNotEmpty(children)){ 44 | c.children = children; 45 | } 46 | } 47 | } 48 | return result; 49 | } 50 | */ 51 | 52 | /** 53 | * 使用递归将数组转为树形结构 54 | * 父ID属性为parent 55 | */ 56 | public static array2Tree (array: any, parentId: number) { 57 | if (Tool.isEmpty(array)) { 58 | return []; 59 | } 60 | 61 | const result = []; 62 | for (let i = 0; i < array.length; i++) { 63 | const c = array[i]; 64 | // console.log(Number(c.parent), Number(parentId)); 65 | if (Number(c.parent) === Number(parentId)) { 66 | result.push(c); 67 | 68 | // 递归查看当前节点对应的子节点 69 | const children = Tool.array2Tree(array, c.id); 70 | if (Tool.isNotEmpty(children)) { 71 | c.children = children; 72 | } 73 | } 74 | } 75 | return result; 76 | } 77 | 78 | /** 79 | * 随机生成[len]长度的[radix]进制数 80 | * @param len 81 | * @param radix 默认62 82 | * @returns {string} 83 | */ 84 | public static uuid (len: number, radix = 62) { 85 | const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''); 86 | const uuid = []; 87 | radix = radix || chars.length; 88 | 89 | for (let i = 0; i < len; i++) { 90 | uuid[i] = chars[0 | Math.random() * radix]; 91 | } 92 | 93 | return uuid.join(''); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/req/DocSaveReq.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.req; 2 | 3 | import javax.validation.constraints.NotNull; 4 | 5 | public class DocSaveReq { 6 | private Long id; 7 | 8 | @NotNull(message = "【电子书】不能为空") 9 | private Long ebookId; 10 | 11 | @NotNull(message = "【父文档】不能为空") 12 | private Long parent; 13 | 14 | @NotNull(message = "【名称】不能为空") 15 | private String name; 16 | 17 | @NotNull(message = "【顺序】不能为空") 18 | private Integer sort; 19 | 20 | private Integer viewCount; 21 | 22 | private Integer voteCount; 23 | 24 | @NotNull(message = "【内容】不能为空") 25 | private String content; 26 | 27 | public Long getId() { 28 | return id; 29 | } 30 | 31 | public void setId(Long id) { 32 | this.id = id; 33 | } 34 | 35 | public Long getEbookId() { 36 | return ebookId; 37 | } 38 | 39 | public void setEbookId(Long ebookId) { 40 | this.ebookId = ebookId; 41 | } 42 | 43 | public Long getParent() { 44 | return parent; 45 | } 46 | 47 | public void setParent(Long parent) { 48 | this.parent = parent; 49 | } 50 | 51 | public String getName() { 52 | return name; 53 | } 54 | 55 | public void setName(String name) { 56 | this.name = name; 57 | } 58 | 59 | public Integer getSort() { 60 | return sort; 61 | } 62 | 63 | public void setSort(Integer sort) { 64 | this.sort = sort; 65 | } 66 | 67 | public Integer getViewCount() { 68 | return viewCount; 69 | } 70 | 71 | public void setViewCount(Integer viewCount) { 72 | this.viewCount = viewCount; 73 | } 74 | 75 | public Integer getVoteCount() { 76 | return voteCount; 77 | } 78 | 79 | public void setVoteCount(Integer voteCount) { 80 | this.voteCount = voteCount; 81 | } 82 | 83 | public String getContent() { 84 | return content; 85 | } 86 | 87 | public void setContent(String content) { 88 | this.content = content; 89 | } 90 | 91 | 92 | @Override 93 | public String toString() { 94 | return "DocSaveReq{" + 95 | "id=" + id + 96 | ", ebookId=" + ebookId + 97 | ", parent=" + parent + 98 | ", name='" + name + '\'' + 99 | ", sort=" + sort + 100 | ", viewCount=" + viewCount + 101 | ", voteCount=" + voteCount + 102 | ", content='" + content + '\'' + 103 | '}'; 104 | } 105 | } -------------------------------------------------------------------------------- /src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | %d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) %blue(%-50logger{50}:%-4line) %thread %green(%-18X{LOG_ID}) %msg%n 9 | 10 | 11 | 12 | 13 | 14 | 15 | ${PATH}/trace.log 16 | 17 | ${PATH}/trace.%d{yyyy-MM-dd}.%i.log 18 | 19 | 10MB 20 | 21 | 22 | 23 | %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %-50logger{50}:%-4line %green(%-18X{LOG_ID}) %msg%n 24 | 25 | 26 | 27 | 28 | ${PATH}/error.log 29 | 30 | ${PATH}/error.%d{yyyy-MM-dd}.%i.log 31 | 32 | 10MB 33 | 34 | 35 | 36 | %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %-50logger{50}:%-4line %green(%-18X{LOG_ID}) %msg%n 37 | 38 | 39 | ERROR 40 | ACCEPT 41 | DENY 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/service/EbookService.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.service; 2 | 3 | import com.github.pagehelper.PageHelper; 4 | import com.github.pagehelper.PageInfo; 5 | import com.xiaolang.wiki.domain.Ebook; 6 | import com.xiaolang.wiki.domain.EbookExample; 7 | import com.xiaolang.wiki.mapper.EbookMapper; 8 | import com.xiaolang.wiki.req.EbookQueryReq; 9 | import com.xiaolang.wiki.req.EbookSaveReq; 10 | import com.xiaolang.wiki.resp.EbookQueryResp; 11 | import com.xiaolang.wiki.resp.PageResp; 12 | import com.xiaolang.wiki.util.CopyUtil; 13 | import com.xiaolang.wiki.util.SnowFlake; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | import org.springframework.stereotype.Service; 17 | import org.springframework.util.ObjectUtils; 18 | 19 | import javax.annotation.Resource; 20 | import java.util.List; 21 | 22 | @Service 23 | public class EbookService { 24 | 25 | private static final Logger LOG = LoggerFactory.getLogger(EbookService.class); 26 | 27 | @Resource 28 | private EbookMapper ebookMapper; 29 | 30 | @Resource 31 | private SnowFlake snowFlake; 32 | 33 | public PageResp list(EbookQueryReq req){ 34 | EbookExample ebookExample = new EbookExample(); 35 | EbookExample.Criteria criteria = ebookExample.createCriteria(); 36 | if(!ObjectUtils.isEmpty(req.getName())){ 37 | criteria.andNameLike("%" + req.getName() + "%"); 38 | } 39 | if (!ObjectUtils.isEmpty(req.getCategoryId2())){ 40 | criteria.andCategory2IdEqualTo(req.getCategoryId2()); 41 | } 42 | PageHelper.startPage(req.getPage(),req.getSize()); 43 | List ebookList = ebookMapper.selectByExample(ebookExample); 44 | 45 | PageInfo pageInfo = new PageInfo<>(ebookList); 46 | LOG.info("总行数: {}",pageInfo.getTotal()); 47 | LOG.info("总页数: {}",pageInfo.getPages()); 48 | //列表复制 49 | List list = CopyUtil.copyList(ebookList, EbookQueryResp.class); 50 | 51 | PageResp pageResp = new PageResp<>(); 52 | pageResp.setTotal(pageInfo.getTotal()); 53 | pageResp.setList(list); 54 | 55 | return pageResp; 56 | } 57 | 58 | /** 59 | * 保存 60 | */ 61 | public void save(EbookSaveReq req){ 62 | Ebook ebook = CopyUtil.copy(req,Ebook.class); 63 | if (ObjectUtils.isEmpty(req.getId())){ 64 | //新增 65 | ebook.setId(snowFlake.nextId()); 66 | ebookMapper.insertSelective(ebook); 67 | }else { 68 | //更新 69 | ebookMapper.updateByPrimaryKey(ebook); 70 | } 71 | } 72 | 73 | /** 74 | * 删除 75 | */ 76 | public void delete(Long id){ 77 | ebookMapper.deleteByPrimaryKey(id); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/resources/mapper/EbookSnapshotMapperCust.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 16 | 17 | insert into ebook_snapshot(ebook_id, `date`, view_count, vote_count, view_increase, vote_increase) 18 | select t1.id, curdate(), 0, 0, 0, 0 19 | from ebook t1 20 | where not exists(select 1 21 | from ebook_snapshot t2 22 | where t1.id = t2.ebook_id 23 | and t2.`date` = curdate()); 24 | 25 | update ebook_snapshot t1, ebook t2 26 | set t1.view_count = t2.view_count, 27 | t1.vote_count = t2.vote_count 28 | where t1.`date` = curdate() 29 | and t1.ebook_id = t2.id; 30 | 31 | update ebook_snapshot t1 left join (select ebook_id, view_count, vote_count 32 | from ebook_snapshot 33 | where `date` = date_sub(curdate(), interval 1 day)) t2 34 | on t1.ebook_id = t2.ebook_id 35 | set t1.view_increase = (t1.view_count - ifnull(t2.view_count, 0)), 36 | t1.vote_increase = (t1.vote_count - ifnull(t2.vote_count, 0)) 37 | where t1.`date` = curdate(); 38 | 39 | 40 | 41 | 57 | 58 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/interceptor/LoginInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.interceptor; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.data.redis.core.RedisTemplate; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.stereotype.Component; 8 | import org.springframework.web.servlet.HandlerInterceptor; 9 | import org.springframework.web.servlet.ModelAndView; 10 | 11 | import javax.annotation.Resource; 12 | import javax.servlet.http.HttpServletRequest; 13 | import javax.servlet.http.HttpServletResponse; 14 | 15 | /** 16 | * 拦截器:Spring框架特有的,常用于登录校验,权限校验,请求日志打印 17 | */ 18 | @Component 19 | public class LoginInterceptor implements HandlerInterceptor { 20 | 21 | private static final Logger LOG = LoggerFactory.getLogger(LoginInterceptor.class); 22 | 23 | @Resource 24 | private RedisTemplate redisTemplate; 25 | 26 | @Override 27 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 28 | // 打印请求信息 29 | LOG.info("------------- LoginInterceptor 开始 -------------"); 30 | long startTime = System.currentTimeMillis(); 31 | request.setAttribute("requestStartTime", startTime); 32 | 33 | // OPTIONS请求不做校验, 34 | // 前后端分离的架构, 前端会发一个OPTIONS请求先做预检, 对预检请求不做校验 35 | if(request.getMethod().toUpperCase().equals("OPTIONS")){ 36 | return true; 37 | } 38 | 39 | String path = request.getRequestURL().toString(); 40 | LOG.info("接口登录拦截:,path:{}", path); 41 | 42 | //获取header的token参数 43 | String token = request.getHeader("token"); 44 | LOG.info("登录校验开始,token:{}", token); 45 | if (token == null || token.isEmpty()) { 46 | LOG.info( "token为空,请求被拦截" ); 47 | response.setStatus(HttpStatus.UNAUTHORIZED.value()); 48 | return false; 49 | } 50 | Object object = redisTemplate.opsForValue().get(token); 51 | if (object == null) { 52 | LOG.warn( "token无效,请求被拦截" ); 53 | response.setStatus(HttpStatus.UNAUTHORIZED.value()); 54 | return false; 55 | } else { 56 | LOG.info("已登录:{}", object); 57 | // LoginUserContext.setUser(JSON.parseObject((String) object, UserLoginResp.class)); 58 | return true; 59 | } 60 | } 61 | 62 | @Override 63 | public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { 64 | long startTime = (Long) request.getAttribute("requestStartTime"); 65 | LOG.info("------------- LoginInterceptor 结束 耗时:{} ms -------------", System.currentTimeMillis() - startTime); 66 | } 67 | 68 | @Override 69 | public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { 70 | // LOG.info("LogInterceptor 结束"); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/domain/Ebook.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.domain; 2 | 3 | public class Ebook { 4 | private Long id; 5 | 6 | private String name; 7 | 8 | private Long category1Id; 9 | 10 | private Long category2Id; 11 | 12 | private String description; 13 | 14 | private String cover; 15 | 16 | private Integer docCount; 17 | 18 | private Integer viewCount; 19 | 20 | private Integer voteCount; 21 | 22 | public Long getId() { 23 | return id; 24 | } 25 | 26 | public void setId(Long id) { 27 | this.id = id; 28 | } 29 | 30 | public String getName() { 31 | return name; 32 | } 33 | 34 | public void setName(String name) { 35 | this.name = name; 36 | } 37 | 38 | public Long getCategory1Id() { 39 | return category1Id; 40 | } 41 | 42 | public void setCategory1Id(Long category1Id) { 43 | this.category1Id = category1Id; 44 | } 45 | 46 | public Long getCategory2Id() { 47 | return category2Id; 48 | } 49 | 50 | public void setCategory2Id(Long category2Id) { 51 | this.category2Id = category2Id; 52 | } 53 | 54 | public String getDescription() { 55 | return description; 56 | } 57 | 58 | public void setDescription(String description) { 59 | this.description = description; 60 | } 61 | 62 | public String getCover() { 63 | return cover; 64 | } 65 | 66 | public void setCover(String cover) { 67 | this.cover = cover; 68 | } 69 | 70 | public Integer getDocCount() { 71 | return docCount; 72 | } 73 | 74 | public void setDocCount(Integer docCount) { 75 | this.docCount = docCount; 76 | } 77 | 78 | public Integer getViewCount() { 79 | return viewCount; 80 | } 81 | 82 | public void setViewCount(Integer viewCount) { 83 | this.viewCount = viewCount; 84 | } 85 | 86 | public Integer getVoteCount() { 87 | return voteCount; 88 | } 89 | 90 | public void setVoteCount(Integer voteCount) { 91 | this.voteCount = voteCount; 92 | } 93 | 94 | @Override 95 | public String toString() { 96 | StringBuilder sb = new StringBuilder(); 97 | sb.append(getClass().getSimpleName()); 98 | sb.append(" ["); 99 | sb.append("Hash = ").append(hashCode()); 100 | sb.append(", id=").append(id); 101 | sb.append(", name=").append(name); 102 | sb.append(", category1Id=").append(category1Id); 103 | sb.append(", category2Id=").append(category2Id); 104 | sb.append(", description=").append(description); 105 | sb.append(", cover=").append(cover); 106 | sb.append(", docCount=").append(docCount); 107 | sb.append(", viewCount=").append(viewCount); 108 | sb.append(", voteCount=").append(voteCount); 109 | sb.append("]"); 110 | return sb.toString(); 111 | } 112 | } -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/resp/EbookQueryResp.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.resp; 2 | 3 | public class EbookQueryResp { 4 | private Long id; 5 | 6 | private String name; 7 | 8 | private Long category1Id; 9 | 10 | private Long category2Id; 11 | 12 | private String description; 13 | 14 | private String cover; 15 | 16 | private Integer docCount; 17 | 18 | private Integer viewCount; 19 | 20 | private Integer voteCount; 21 | 22 | public Long getId() { 23 | return id; 24 | } 25 | 26 | public void setId(Long id) { 27 | this.id = id; 28 | } 29 | 30 | public String getName() { 31 | return name; 32 | } 33 | 34 | public void setName(String name) { 35 | this.name = name; 36 | } 37 | 38 | public Long getCategory1Id() { 39 | return category1Id; 40 | } 41 | 42 | public void setCategory1Id(Long category1Id) { 43 | this.category1Id = category1Id; 44 | } 45 | 46 | public Long getCategory2Id() { 47 | return category2Id; 48 | } 49 | 50 | public void setCategory2Id(Long category2Id) { 51 | this.category2Id = category2Id; 52 | } 53 | 54 | public String getDescription() { 55 | return description; 56 | } 57 | 58 | public void setDescription(String description) { 59 | this.description = description; 60 | } 61 | 62 | public String getCover() { 63 | return cover; 64 | } 65 | 66 | public void setCover(String cover) { 67 | this.cover = cover; 68 | } 69 | 70 | public Integer getDocCount() { 71 | return docCount; 72 | } 73 | 74 | public void setDocCount(Integer docCount) { 75 | this.docCount = docCount; 76 | } 77 | 78 | public Integer getViewCount() { 79 | return viewCount; 80 | } 81 | 82 | public void setViewCount(Integer viewCount) { 83 | this.viewCount = viewCount; 84 | } 85 | 86 | public Integer getVoteCount() { 87 | return voteCount; 88 | } 89 | 90 | public void setVoteCount(Integer voteCount) { 91 | this.voteCount = voteCount; 92 | } 93 | 94 | @Override 95 | public String toString() { 96 | StringBuilder sb = new StringBuilder(); 97 | sb.append(getClass().getSimpleName()); 98 | sb.append(" ["); 99 | sb.append("Hash = ").append(hashCode()); 100 | sb.append(", id=").append(id); 101 | sb.append(", name=").append(name); 102 | sb.append(", category1Id=").append(category1Id); 103 | sb.append(", category2Id=").append(category2Id); 104 | sb.append(", description=").append(description); 105 | sb.append(", cover=").append(cover); 106 | sb.append(", docCount=").append(docCount); 107 | sb.append(", viewCount=").append(viewCount); 108 | sb.append(", voteCount=").append(voteCount); 109 | sb.append("]"); 110 | return sb.toString(); 111 | } 112 | } -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/service/CategoryService.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.service; 2 | 3 | import com.github.pagehelper.PageHelper; 4 | import com.github.pagehelper.PageInfo; 5 | import com.xiaolang.wiki.domain.Category; 6 | import com.xiaolang.wiki.domain.CategoryExample; 7 | import com.xiaolang.wiki.mapper.CategoryMapper; 8 | import com.xiaolang.wiki.req.CategoryQueryReq; 9 | import com.xiaolang.wiki.req.CategorySaveReq; 10 | import com.xiaolang.wiki.resp.CategoryQueryResp; 11 | import com.xiaolang.wiki.resp.PageResp; 12 | import com.xiaolang.wiki.util.CopyUtil; 13 | import com.xiaolang.wiki.util.SnowFlake; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | import org.springframework.stereotype.Service; 17 | import org.springframework.util.ObjectUtils; 18 | 19 | import javax.annotation.Resource; 20 | import java.util.List; 21 | 22 | @Service 23 | public class CategoryService { 24 | 25 | private static final Logger LOG = LoggerFactory.getLogger(CategoryService.class); 26 | 27 | @Resource 28 | private CategoryMapper categoryMapper; 29 | 30 | @Resource 31 | private SnowFlake snowFlake; 32 | 33 | public List all(){ 34 | CategoryExample categoryExample = new CategoryExample(); 35 | categoryExample.setOrderByClause("sort asc"); 36 | List categoryList = categoryMapper.selectByExample(categoryExample); 37 | 38 | //列表复制 39 | List list = CopyUtil.copyList(categoryList, CategoryQueryResp.class); 40 | 41 | return list; 42 | } 43 | 44 | public PageResp list(CategoryQueryReq req){ 45 | CategoryExample categoryExample = new CategoryExample(); 46 | categoryExample.setOrderByClause("sort asc"); 47 | CategoryExample.Criteria criteria = categoryExample.createCriteria(); 48 | PageHelper.startPage(req.getPage(),req.getSize()); 49 | List categoryList = categoryMapper.selectByExample(categoryExample); 50 | 51 | PageInfo pageInfo = new PageInfo<>(categoryList); 52 | LOG.info("总行数: {}",pageInfo.getTotal()); 53 | LOG.info("总页数: {}",pageInfo.getPages()); 54 | 55 | //列表复制 56 | List list = CopyUtil.copyList(categoryList, CategoryQueryResp.class); 57 | 58 | PageResp pageResp = new PageResp<>(); 59 | pageResp.setTotal(pageInfo.getTotal()); 60 | pageResp.setList(list); 61 | 62 | return pageResp; 63 | } 64 | 65 | /** 66 | * 保存 67 | */ 68 | public void save(CategorySaveReq req){ 69 | Category category = CopyUtil.copy(req,Category.class); 70 | if (ObjectUtils.isEmpty(req.getId())){ 71 | //新增 72 | category.setId(snowFlake.nextId()); 73 | categoryMapper.insert(category); 74 | }else { 75 | //更新 76 | categoryMapper.updateByPrimaryKey(category); 77 | } 78 | } 79 | 80 | /** 81 | * 删除 82 | */ 83 | public void delete(Long id){ 84 | categoryMapper.deleteByPrimaryKey(id); 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/req/EbookSaveReq.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.req; 2 | 3 | import javax.validation.constraints.NotNull; 4 | 5 | public class EbookSaveReq { 6 | private Long id; 7 | 8 | @NotNull(message = "【名称】不能为空") 9 | private String name; 10 | 11 | private Long category1Id; 12 | 13 | @NotNull(message = "【目录】不能为空") 14 | private Long category2Id; 15 | 16 | private String description; 17 | 18 | private String cover; 19 | 20 | private Integer docCount; 21 | 22 | private Integer viewCount; 23 | 24 | private Integer voteCount; 25 | 26 | public Long getId() { 27 | return id; 28 | } 29 | 30 | public void setId(Long id) { 31 | this.id = id; 32 | } 33 | 34 | public String getName() { 35 | return name; 36 | } 37 | 38 | public void setName(String name) { 39 | this.name = name; 40 | } 41 | 42 | public Long getCategory1Id() { 43 | return category1Id; 44 | } 45 | 46 | public void setCategory1Id(Long category1Id) { 47 | this.category1Id = category1Id; 48 | } 49 | 50 | public Long getCategory2Id() { 51 | return category2Id; 52 | } 53 | 54 | public void setCategory2Id(Long category2Id) { 55 | this.category2Id = category2Id; 56 | } 57 | 58 | public String getDescription() { 59 | return description; 60 | } 61 | 62 | public void setDescription(String description) { 63 | this.description = description; 64 | } 65 | 66 | public String getCover() { 67 | return cover; 68 | } 69 | 70 | public void setCover(String cover) { 71 | this.cover = cover; 72 | } 73 | 74 | public Integer getDocCount() { 75 | return docCount; 76 | } 77 | 78 | public void setDocCount(Integer docCount) { 79 | this.docCount = docCount; 80 | } 81 | 82 | public Integer getViewCount() { 83 | return viewCount; 84 | } 85 | 86 | public void setViewCount(Integer viewCount) { 87 | this.viewCount = viewCount; 88 | } 89 | 90 | public Integer getVoteCount() { 91 | return voteCount; 92 | } 93 | 94 | public void setVoteCount(Integer voteCount) { 95 | this.voteCount = voteCount; 96 | } 97 | 98 | @Override 99 | public String toString() { 100 | StringBuilder sb = new StringBuilder(); 101 | sb.append(getClass().getSimpleName()); 102 | sb.append(" ["); 103 | sb.append("Hash = ").append(hashCode()); 104 | sb.append(", id=").append(id); 105 | sb.append(", name=").append(name); 106 | sb.append(", category1Id=").append(category1Id); 107 | sb.append(", category2Id=").append(category2Id); 108 | sb.append(", description=").append(description); 109 | sb.append(", cover=").append(cover); 110 | sb.append(", docCount=").append(docCount); 111 | sb.append(", viewCount=").append(viewCount); 112 | sb.append(", voteCount=").append(voteCount); 113 | sb.append("]"); 114 | return sb.toString(); 115 | } 116 | } -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/controller/ControllerExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.controller; 2 | 3 | // import com.xiaolang.wiki.exception.BusinessException; 4 | import com.xiaolang.wiki.exception.BusinessException; 5 | import com.xiaolang.wiki.resp.CommonResp; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.validation.BindException; 9 | import org.springframework.web.bind.annotation.ControllerAdvice; 10 | import org.springframework.web.bind.annotation.ExceptionHandler; 11 | import org.springframework.web.bind.annotation.ResponseBody; 12 | 13 | /** 14 | * 统一异常处理、数据预处理等 15 | */ 16 | @ControllerAdvice 17 | public class ControllerExceptionHandler { 18 | 19 | private static final Logger LOG = LoggerFactory.getLogger(ControllerExceptionHandler.class); 20 | 21 | /** 22 | * 校验异常统一处理 23 | * @param e 24 | * @return 25 | */ 26 | @ExceptionHandler(value = BindException.class) 27 | @ResponseBody 28 | public CommonResp validExceptionHandler(BindException e) { 29 | CommonResp commonResp = new CommonResp(); 30 | LOG.warn("参数校验失败:{}", e.getBindingResult().getAllErrors().get(0).getDefaultMessage()); 31 | commonResp.setSuccess(false); 32 | commonResp.setMessage(e.getBindingResult().getAllErrors().get(0).getDefaultMessage()); 33 | return commonResp; 34 | } 35 | 36 | /** 37 | * 校验异常统一处理 38 | * @param e 39 | * @return 40 | */ 41 | @ExceptionHandler(value = BusinessException.class) 42 | @ResponseBody 43 | public CommonResp validExceptionHandler(BusinessException e) { 44 | CommonResp commonResp = new CommonResp(); 45 | LOG.warn("业务异常: {}", e.getCode().getDesc()); 46 | commonResp.setSuccess(false); 47 | commonResp.setMessage(e.getCode().getDesc()); 48 | return commonResp; 49 | } 50 | 51 | /** 52 | * 校验异常统一处理 53 | * @param e 54 | * @return 55 | */ 56 | @ExceptionHandler(value = Exception.class) 57 | @ResponseBody 58 | public CommonResp validExceptionHandler(Exception e) { 59 | CommonResp commonResp = new CommonResp(); 60 | LOG.error("系统异常: ", e); 61 | commonResp.setSuccess(false); 62 | commonResp.setMessage("系统出现异常,请联系管理员"); 63 | return commonResp; 64 | } 65 | 66 | /** 67 | * 校验异常统一处理 68 | * @param e 69 | * @return 70 | */ 71 | /*@ExceptionHandler(value = BusinessException.class) 72 | @ResponseBody 73 | public CommonResp validExceptionHandler(BusinessException e) { 74 | CommonResp commonResp = new CommonResp(); 75 | LOG.warn("业务异常:{}", e.getCode().getDesc()); 76 | commonResp.setSuccess(false); 77 | commonResp.setMessage(e.getCode().getDesc()); 78 | return commonResp; 79 | }*/ 80 | 81 | /** 82 | * 校验异常统一处理 83 | * @param e 84 | * @return 85 | */ 86 | /*@ExceptionHandler(value = Exception.class) 87 | @ResponseBody 88 | public CommonResp validExceptionHandler(Exception e) { 89 | CommonResp commonResp = new CommonResp(); 90 | LOG.error("系统异常:", e); 91 | commonResp.setSuccess(false); 92 | commonResp.setMessage("系统出现异常,请联系管理员"); 93 | return commonResp; 94 | }*/ 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.controller; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.xiaolang.wiki.req.UserLoginReq; 5 | import com.xiaolang.wiki.req.UserQueryReq; 6 | import com.xiaolang.wiki.req.UserResetPasswordReq; 7 | import com.xiaolang.wiki.req.UserSaveReq; 8 | import com.xiaolang.wiki.resp.CommonResp; 9 | import com.xiaolang.wiki.resp.PageResp; 10 | import com.xiaolang.wiki.resp.UserLoginResp; 11 | import com.xiaolang.wiki.resp.UserQueryResp; 12 | import com.xiaolang.wiki.service.UserService; 13 | import com.xiaolang.wiki.util.SnowFlake; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | import org.springframework.data.redis.core.RedisTemplate; 17 | import org.springframework.util.DigestUtils; 18 | import org.springframework.web.bind.annotation.*; 19 | 20 | import javax.annotation.Resource; 21 | import javax.validation.Valid; 22 | import java.util.concurrent.TimeUnit; 23 | 24 | @RestController 25 | @RequestMapping("/user") 26 | public class UserController { 27 | 28 | private static final Logger LOG = LoggerFactory.getLogger(UserController.class); 29 | 30 | @Resource 31 | private UserService userService; 32 | 33 | @Resource 34 | private SnowFlake snowFlake; 35 | 36 | @Resource 37 | private RedisTemplate redisTemplate; 38 | 39 | @GetMapping("/list") 40 | public CommonResp list(@Valid UserQueryReq req){ 41 | CommonResp> resp = new CommonResp<>(); 42 | PageResp list = userService.list(req); 43 | resp.setContent(list); 44 | return resp; 45 | } 46 | 47 | @PostMapping("/save") 48 | public CommonResp save(@Valid @RequestBody UserSaveReq req){ 49 | req.setPassword(DigestUtils.md5DigestAsHex(req.getPassword().getBytes())); 50 | CommonResp resp = new CommonResp(); 51 | userService.save(req); 52 | return resp; 53 | } 54 | 55 | 56 | // 删除 57 | @DeleteMapping("/delete/{id}") 58 | public CommonResp delete(@PathVariable Long id){ 59 | CommonResp resp = new CommonResp<>(); 60 | userService.delete(id); 61 | return resp; 62 | } 63 | 64 | // 更改用户密码 65 | @PostMapping("/reset-password") 66 | public CommonResp resetPassword(@Valid @RequestBody UserResetPasswordReq req){ 67 | req.setPassword(DigestUtils.md5DigestAsHex(req.getPassword().getBytes())); 68 | CommonResp resp = new CommonResp(); 69 | userService.resetPassword(req); 70 | return resp; 71 | } 72 | 73 | // 用户登录 74 | @PostMapping("/login") 75 | public CommonResp login(@Valid @RequestBody UserLoginReq req){ 76 | req.setPassword(DigestUtils.md5DigestAsHex(req.getPassword().getBytes())); 77 | CommonResp resp = new CommonResp(); 78 | UserLoginResp userLoginResp = userService.login(req); 79 | 80 | // 生成单点登录token,并放入redis中 81 | Long token = snowFlake.nextId(); 82 | LOG.info("生成单点登录token: {} ,并放入redis中",token); 83 | userLoginResp.setToken(token.toString()); 84 | redisTemplate.opsForValue().set(token.toString(), JSONObject.toJSONString(userLoginResp), 3600 * 24, TimeUnit.SECONDS); 85 | 86 | resp.setContent(userLoginResp); 87 | return resp; 88 | } 89 | 90 | // 退出登录 91 | @GetMapping("/logout/{token}") 92 | public CommonResp logout(@PathVariable String token) { 93 | CommonResp resp = new CommonResp(); 94 | redisTemplate.delete(token); 95 | LOG.info("从redis中删除token: {}", token); 96 | return resp; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/util/SnowFlake.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.util; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import java.text.ParseException; 6 | 7 | /** 8 | * Twitter的分布式自增ID雪花算法 9 | * 10 | * 时间间隔大了就会导致生成的id为17或者更多,前端number最大为16位, 11 | * 所以再从前端网后端传参时会出现精度缺失,暂未解决 12 | **/ 13 | @Component 14 | public class SnowFlake { 15 | 16 | /** 17 | * 起始的时间戳 18 | */ 19 | private final static long START_STMP = 1617235200000L; // 2021-04-01 00:00:00 20 | 21 | /** 22 | * 每一部分占用的位数 23 | */ 24 | private final static long SEQUENCE_BIT = 12; //序列号占用的位数 25 | private final static long MACHINE_BIT = 5; //机器标识占用的位数 26 | private final static long DATACENTER_BIT = 5;//数据中心占用的位数 27 | 28 | /** 29 | * 每一部分的最大值 30 | */ 31 | private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT); 32 | private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT); 33 | private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT); 34 | 35 | /** 36 | * 每一部分向左的位移 37 | */ 38 | private final static long MACHINE_LEFT = SEQUENCE_BIT; 39 | private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT; 40 | private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT; 41 | 42 | private long datacenterId = 1; //数据中心 43 | private long machineId = 1; //机器标识 44 | private long sequence = 0L; //序列号 45 | private long lastStmp = -1L;//上一次时间戳 46 | 47 | public SnowFlake() { 48 | } 49 | 50 | public SnowFlake(long datacenterId, long machineId) { 51 | if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) { 52 | throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0"); 53 | } 54 | if (machineId > MAX_MACHINE_NUM || machineId < 0) { 55 | throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0"); 56 | } 57 | this.datacenterId = datacenterId; 58 | this.machineId = machineId; 59 | } 60 | 61 | /** 62 | * 产生下一个ID 63 | * 64 | * @return 65 | */ 66 | public synchronized long nextId() { 67 | long currStmp = getNewstmp(); 68 | if (currStmp < lastStmp) { 69 | throw new RuntimeException("Clock moved backwards. Refusing to generate id"); 70 | } 71 | 72 | if (currStmp == lastStmp) { 73 | //相同毫秒内,序列号自增 74 | sequence = (sequence + 1) & MAX_SEQUENCE; 75 | //同一毫秒的序列数已经达到最大 76 | if (sequence == 0L) { 77 | currStmp = getNextMill(); 78 | } 79 | } else { 80 | //不同毫秒内,序列号置为0 81 | sequence = 0L; 82 | } 83 | 84 | lastStmp = currStmp; 85 | 86 | return (currStmp - START_STMP) << TIMESTMP_LEFT //时间戳部分 87 | | datacenterId << DATACENTER_LEFT //数据中心部分 88 | | machineId << MACHINE_LEFT //机器标识部分 89 | | sequence; //序列号部分 90 | } 91 | 92 | private long getNextMill() { 93 | long mill = getNewstmp(); 94 | while (mill <= lastStmp) { 95 | mill = getNewstmp(); 96 | } 97 | return mill; 98 | } 99 | 100 | private long getNewstmp() { 101 | return System.currentTimeMillis(); 102 | } 103 | 104 | public static void main(String[] args) throws ParseException { 105 | // 时间戳 106 | /*System.out.println(System.currentTimeMillis()); 107 | System.out.println(new Date().getTime()); 108 | 109 | String dateTime = "2021-04-01 08:00:00"; 110 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); 111 | System.out.println(sdf.parse(dateTime).getTime());*/ 112 | 113 | SnowFlake snowFlake = new SnowFlake(1, 1); 114 | 115 | long start = System.currentTimeMillis(); 116 | for (int i = 0; i < 10; i++) { 117 | System.out.println(snowFlake.nextId()); 118 | System.out.println(System.currentTimeMillis() - start); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.4.4 9 | 10 | 11 | com.xiaolang 12 | wiki 13 | 0.0.1-SNAPSHOT 14 | wiki 15 | wiki 16 | 17 | 1.8 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-web 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-devtools 28 | 29 | 30 | 31 | 32 | org.mybatis.spring.boot 33 | mybatis-spring-boot-starter 34 | 2.1.3 35 | 36 | 37 | 38 | 39 | mysql 40 | mysql-connector-java 41 | 8.0.22 42 | 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-starter-test 47 | test 48 | 49 | 50 | 51 | org.springframework.boot 52 | spring-boot-starter-aop 53 | 54 | 55 | 56 | 57 | org.springframework.boot 58 | spring-boot-starter-data-redis 59 | 60 | 61 | 62 | org.springframework.boot 63 | spring-boot-starter-validation 64 | 65 | 66 | 67 | org.springframework.boot 68 | spring-boot-starter-websocket 69 | 70 | 71 | 72 | com.alibaba 73 | fastjson 74 | 1.2.70 75 | 76 | 77 | 78 | com.github.pagehelper 79 | pagehelper-spring-boot-starter 80 | 1.2.13 81 | 82 | 83 | 84 | 89 | 90 | 91 | 92 | /dist/${artifactId} 93 | 94 | 95 | org.springframework.boot 96 | spring-boot-maven-plugin 97 | 98 | 99 | 100 | 101 | org.mybatis.generator 102 | mybatis-generator-maven-plugin 103 | 1.4.0 104 | 105 | src/main/resources/generator/generator-config.xml 106 | true 107 | true 108 | 109 | 110 | 111 | mysql 112 | mysql-connector-java 113 | 8.0.22 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/aspect/LogAspect.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.aspect; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.alibaba.fastjson.support.spring.PropertyPreFilters; 5 | import com.xiaolang.wiki.util.RequestContext; 6 | import com.xiaolang.wiki.util.SnowFlake; 7 | import org.aspectj.lang.JoinPoint; 8 | import org.aspectj.lang.ProceedingJoinPoint; 9 | import org.aspectj.lang.Signature; 10 | import org.aspectj.lang.annotation.Around; 11 | import org.aspectj.lang.annotation.Aspect; 12 | import org.aspectj.lang.annotation.Before; 13 | import org.aspectj.lang.annotation.Pointcut; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | import org.slf4j.MDC; 17 | import org.springframework.stereotype.Component; 18 | import org.springframework.web.context.request.RequestContextHolder; 19 | import org.springframework.web.context.request.ServletRequestAttributes; 20 | import org.springframework.web.multipart.MultipartFile; 21 | 22 | import javax.annotation.Resource; 23 | import javax.servlet.ServletRequest; 24 | import javax.servlet.ServletResponse; 25 | import javax.servlet.http.HttpServletRequest; 26 | 27 | @Aspect 28 | @Component 29 | public class LogAspect { 30 | 31 | private final static Logger LOG = LoggerFactory.getLogger(LogAspect.class); 32 | @Resource 33 | private SnowFlake snowFlake; 34 | 35 | /** 定义一个切点 */ 36 | @Pointcut("execution(public * com.xiaolang.*.controller..*Controller.*(..))") 37 | public void controllerPointcut() {} 38 | 39 | @Before("controllerPointcut()") 40 | public void doBefore(JoinPoint joinPoint) throws Throwable { 41 | // 增加日志流水号 42 | MDC.put("LOG_ID", String.valueOf(snowFlake.nextId())); 43 | 44 | // 开始打印请求日志 45 | ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); 46 | HttpServletRequest request = attributes.getRequest(); 47 | Signature signature = joinPoint.getSignature(); 48 | String name = signature.getName(); 49 | 50 | // 打印请求信息 51 | LOG.info("------------- 开始 -------------"); 52 | LOG.info("请求地址: {} {}", request.getRequestURL().toString(), request.getMethod()); 53 | LOG.info("类名方法: {}.{}", signature.getDeclaringTypeName(), name); 54 | LOG.info("远程地址: {}", request.getRemoteAddr()); 55 | 56 | RequestContext.setRemoteAddr(getRemoteIp(request)); 57 | 58 | // 打印请求参数 59 | Object[] args = joinPoint.getArgs(); 60 | // LOG.info("请求参数: {}", JSONObject.toJSONString(args)); 61 | 62 | Object[] arguments = new Object[args.length]; 63 | for (int i = 0; i < args.length; i++) { 64 | if (args[i] instanceof ServletRequest 65 | || args[i] instanceof ServletResponse 66 | || args[i] instanceof MultipartFile) { 67 | continue; 68 | } 69 | arguments[i] = args[i]; 70 | } 71 | // 排除字段,敏感字段或太长的字段不显示 72 | String[] excludeProperties = {"password", "file"}; 73 | PropertyPreFilters filters = new PropertyPreFilters(); 74 | PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter(); 75 | excludefilter.addExcludes(excludeProperties); 76 | LOG.info("请求参数: {}", JSONObject.toJSONString(arguments, excludefilter)); 77 | } 78 | 79 | @Around("controllerPointcut()") 80 | public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { 81 | long startTime = System.currentTimeMillis(); 82 | Object result = proceedingJoinPoint.proceed(); 83 | // 排除字段,敏感字段或太长的字段不显示 84 | String[] excludeProperties = {"password", "file"}; 85 | PropertyPreFilters filters = new PropertyPreFilters(); 86 | PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter(); 87 | excludefilter.addExcludes(excludeProperties); 88 | LOG.info("返回结果: {}", JSONObject.toJSONString(result, excludefilter)); 89 | LOG.info("------------- 结束 耗时:{} ms -------------", System.currentTimeMillis() - startTime); 90 | return result; 91 | } 92 | 93 | /** 94 | * 使用nginx做反向代理,需要用该方法才能取到真实的远程IP 95 | * @param request 96 | * @return 97 | */ 98 | public String getRemoteIp(HttpServletRequest request) { 99 | String ip = request.getHeader("x-forwarded-for"); 100 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 101 | ip = request.getHeader("Proxy-Client-IP"); 102 | } 103 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 104 | ip = request.getHeader("WL-Proxy-Client-IP"); 105 | } 106 | if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 107 | ip = request.getRemoteAddr(); 108 | } 109 | return ip; 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.service; 2 | 3 | import com.github.pagehelper.PageHelper; 4 | import com.github.pagehelper.PageInfo; 5 | import com.xiaolang.wiki.domain.User; 6 | import com.xiaolang.wiki.domain.UserExample; 7 | import com.xiaolang.wiki.exception.BusinessException; 8 | import com.xiaolang.wiki.exception.BusinessExceptionCode; 9 | import com.xiaolang.wiki.mapper.UserMapper; 10 | import com.xiaolang.wiki.req.UserLoginReq; 11 | import com.xiaolang.wiki.req.UserQueryReq; 12 | import com.xiaolang.wiki.req.UserResetPasswordReq; 13 | import com.xiaolang.wiki.req.UserSaveReq; 14 | import com.xiaolang.wiki.resp.PageResp; 15 | import com.xiaolang.wiki.resp.UserLoginResp; 16 | import com.xiaolang.wiki.resp.UserQueryResp; 17 | import com.xiaolang.wiki.util.CopyUtil; 18 | import com.xiaolang.wiki.util.SnowFlake; 19 | import org.slf4j.Logger; 20 | import org.slf4j.LoggerFactory; 21 | import org.springframework.stereotype.Service; 22 | import org.springframework.util.CollectionUtils; 23 | import org.springframework.util.ObjectUtils; 24 | 25 | import javax.annotation.Resource; 26 | import java.util.List; 27 | 28 | @Service 29 | public class UserService { 30 | 31 | private static final Logger LOG = LoggerFactory.getLogger(UserService.class); 32 | 33 | @Resource 34 | private UserMapper userMapper; 35 | 36 | @Resource 37 | private SnowFlake snowFlake; 38 | 39 | public PageResp list(UserQueryReq req){ 40 | UserExample userExample = new UserExample(); 41 | UserExample.Criteria criteria = userExample.createCriteria(); 42 | if (!ObjectUtils.isEmpty(req.getLoginName())) { 43 | criteria.andLoginNameEqualTo(req.getLoginName()); 44 | } 45 | PageHelper.startPage(req.getPage(),req.getSize()); 46 | List userList = userMapper.selectByExample(userExample); 47 | 48 | PageInfo pageInfo = new PageInfo<>(userList); 49 | LOG.info("总行数: {}",pageInfo.getTotal()); 50 | LOG.info("总页数: {}",pageInfo.getPages()); 51 | 52 | //列表复制 53 | List list = CopyUtil.copyList(userList, UserQueryResp.class); 54 | 55 | PageResp pageResp = new PageResp<>(); 56 | pageResp.setTotal(pageInfo.getTotal()); 57 | pageResp.setList(list); 58 | 59 | return pageResp; 60 | } 61 | 62 | /** 63 | * 保存 64 | */ 65 | public void save(UserSaveReq req){ 66 | User user = CopyUtil.copy(req,User.class); 67 | if (ObjectUtils.isEmpty(req.getId())){ 68 | 69 | User userDB = selectByLoginName(req.getLoginName()); 70 | if (ObjectUtils.isEmpty(userDB)) { 71 | //新增 72 | user.setId(snowFlake.nextId()); 73 | userMapper.insert(user); 74 | } else { 75 | // 用户名已存在 76 | throw new BusinessException(BusinessExceptionCode.USER_LOGIN_NAME_EXIST); 77 | } 78 | 79 | }else { 80 | //更新 81 | user.setLoginName(null); 82 | user.setPassword(null); 83 | userMapper.updateByPrimaryKeySelective(user); 84 | } 85 | } 86 | 87 | /** 88 | * 删除 89 | */ 90 | public void delete(Long id){ 91 | userMapper.deleteByPrimaryKey(id); 92 | } 93 | 94 | public User selectByLoginName(String loginName) { 95 | UserExample userExample = new UserExample(); 96 | UserExample.Criteria criteria = userExample.createCriteria(); 97 | criteria.andLoginNameEqualTo(loginName); 98 | List userList = userMapper.selectByExample(userExample); 99 | if (CollectionUtils.isEmpty(userList)) { 100 | return null; 101 | } else { 102 | return userList.get(0); 103 | } 104 | } 105 | 106 | /** 107 | * 修改用户密码 108 | */ 109 | public void resetPassword(UserResetPasswordReq req){ 110 | User user = CopyUtil.copy(req,User.class); 111 | userMapper.updateByPrimaryKeySelective(user); 112 | } 113 | 114 | /** 115 | * 用户登录 116 | */ 117 | public UserLoginResp login(UserLoginReq req) { 118 | User userDb = selectByLoginName(req.getLoginName()); 119 | if (ObjectUtils.isEmpty(userDb)) { 120 | // 用户名不存在 121 | LOG.info("用户名不存在, {}", req.getLoginName()); 122 | throw new BusinessException(BusinessExceptionCode.LOGIN_USER_ERROR); 123 | } else { 124 | if (userDb.getPassword().equals(req.getPassword())) { 125 | // 登录成功 126 | UserLoginResp userLoginResp = CopyUtil.copy(userDb, UserLoginResp.class); 127 | return userLoginResp; 128 | } else { 129 | // 密码不正确 130 | LOG.info("密码不对, 输入密码: {}, 数据库密码: {}", req.getPassword(), userDb.getPassword()); 131 | throw new BusinessException(BusinessExceptionCode.LOGIN_USER_ERROR); 132 | } 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007-present the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import java.net.*; 18 | import java.io.*; 19 | import java.nio.channels.*; 20 | import java.util.Properties; 21 | 22 | public class MavenWrapperDownloader { 23 | 24 | private static final String WRAPPER_VERSION = "0.5.6"; 25 | /** 26 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 27 | */ 28 | private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" 29 | + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; 30 | 31 | /** 32 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 33 | * use instead of the default one. 34 | */ 35 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 36 | ".mvn/wrapper/maven-wrapper.properties"; 37 | 38 | /** 39 | * Path where the maven-wrapper.jar will be saved to. 40 | */ 41 | private static final String MAVEN_WRAPPER_JAR_PATH = 42 | ".mvn/wrapper/maven-wrapper.jar"; 43 | 44 | /** 45 | * Name of the property which should be used to override the default download url for the wrapper. 46 | */ 47 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 48 | 49 | public static void main(String args[]) { 50 | System.out.println("- Downloader started"); 51 | File baseDirectory = new File(args[0]); 52 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 53 | 54 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 55 | // wrapperUrl parameter. 56 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 57 | String url = DEFAULT_DOWNLOAD_URL; 58 | if (mavenWrapperPropertyFile.exists()) { 59 | FileInputStream mavenWrapperPropertyFileInputStream = null; 60 | try { 61 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 62 | Properties mavenWrapperProperties = new Properties(); 63 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 64 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 65 | } catch (IOException e) { 66 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 67 | } finally { 68 | try { 69 | if (mavenWrapperPropertyFileInputStream != null) { 70 | mavenWrapperPropertyFileInputStream.close(); 71 | } 72 | } catch (IOException e) { 73 | // Ignore ... 74 | } 75 | } 76 | } 77 | System.out.println("- Downloading from: " + url); 78 | 79 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 80 | if (!outputFile.getParentFile().exists()) { 81 | if (!outputFile.getParentFile().mkdirs()) { 82 | System.out.println( 83 | "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 84 | } 85 | } 86 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 87 | try { 88 | downloadFileFromURL(url, outputFile); 89 | System.out.println("Done"); 90 | System.exit(0); 91 | } catch (Throwable e) { 92 | System.out.println("- Error downloading"); 93 | e.printStackTrace(); 94 | System.exit(1); 95 | } 96 | } 97 | 98 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 99 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { 100 | String username = System.getenv("MVNW_USERNAME"); 101 | char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); 102 | Authenticator.setDefault(new Authenticator() { 103 | @Override 104 | protected PasswordAuthentication getPasswordAuthentication() { 105 | return new PasswordAuthentication(username, password); 106 | } 107 | }); 108 | } 109 | URL website = new URL(urlString); 110 | ReadableByteChannel rbc; 111 | rbc = Channels.newChannel(website.openStream()); 112 | FileOutputStream fos = new FileOutputStream(destination); 113 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 114 | fos.close(); 115 | rbc.close(); 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /web/src/views/home.vue: -------------------------------------------------------------------------------- 1 | 67 | 68 | 167 | 168 | 177 | -------------------------------------------------------------------------------- /src/main/java/com/xiaolang/wiki/service/DocService.java: -------------------------------------------------------------------------------- 1 | package com.xiaolang.wiki.service; 2 | 3 | import com.github.pagehelper.PageHelper; 4 | import com.github.pagehelper.PageInfo; 5 | import com.xiaolang.wiki.domain.Content; 6 | import com.xiaolang.wiki.domain.Doc; 7 | import com.xiaolang.wiki.domain.DocExample; 8 | import com.xiaolang.wiki.exception.BusinessException; 9 | import com.xiaolang.wiki.exception.BusinessExceptionCode; 10 | import com.xiaolang.wiki.mapper.ContentMapper; 11 | import com.xiaolang.wiki.mapper.DocMapper; 12 | import com.xiaolang.wiki.mapper.DocMapperCust; 13 | import com.xiaolang.wiki.req.DocQueryReq; 14 | import com.xiaolang.wiki.req.DocSaveReq; 15 | import com.xiaolang.wiki.resp.DocQueryResp; 16 | import com.xiaolang.wiki.resp.PageResp; 17 | import com.xiaolang.wiki.util.CopyUtil; 18 | import com.xiaolang.wiki.util.RedisUtil; 19 | import com.xiaolang.wiki.util.RequestContext; 20 | import com.xiaolang.wiki.util.SnowFlake; 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | import org.slf4j.MDC; 24 | import org.springframework.stereotype.Service; 25 | import org.springframework.transaction.annotation.Transactional; 26 | import org.springframework.util.ObjectUtils; 27 | 28 | import javax.annotation.Resource; 29 | import java.util.ArrayList; 30 | import java.util.List; 31 | 32 | @Service 33 | public class DocService { 34 | 35 | private static final Logger LOG = LoggerFactory.getLogger(DocService.class); 36 | 37 | @Resource 38 | private DocMapper docMapper; 39 | 40 | @Resource 41 | private DocMapperCust docMapperCust; 42 | 43 | @Resource 44 | private ContentMapper contentMapper; 45 | 46 | @Resource 47 | private SnowFlake snowFlake; 48 | 49 | @Resource 50 | public RedisUtil redisUtil; 51 | 52 | @Resource 53 | public WsService wsService; 54 | 55 | /* @Resource 56 | private RocketMQTemplate rocketMQTemplate;*/ 57 | 58 | public List all(Long ebookId){ 59 | DocExample docExample = new DocExample(); 60 | docExample.createCriteria().andEbookIdEqualTo(ebookId); 61 | docExample.setOrderByClause("sort asc"); 62 | List docList = docMapper.selectByExample(docExample); 63 | 64 | //列表复制 65 | List list = CopyUtil.copyList(docList, DocQueryResp.class); 66 | 67 | return list; 68 | } 69 | 70 | public PageResp list(DocQueryReq req){ 71 | DocExample docExample = new DocExample(); 72 | docExample.setOrderByClause("sort asc"); 73 | DocExample.Criteria criteria = docExample.createCriteria(); 74 | PageHelper.startPage(req.getPage(),req.getSize()); 75 | List docList = docMapper.selectByExample(docExample); 76 | 77 | PageInfo pageInfo = new PageInfo<>(docList); 78 | LOG.info("总行数: {}",pageInfo.getTotal()); 79 | LOG.info("总页数: {}",pageInfo.getPages()); 80 | 81 | //列表复制 82 | List list = CopyUtil.copyList(docList, DocQueryResp.class); 83 | 84 | PageResp pageResp = new PageResp<>(); 85 | pageResp.setTotal(pageInfo.getTotal()); 86 | pageResp.setList(list); 87 | 88 | return pageResp; 89 | } 90 | 91 | /** 92 | * 保存 93 | */ 94 | @Transactional 95 | public void save(DocSaveReq req){ 96 | Doc doc = CopyUtil.copy(req,Doc.class); 97 | Content content = CopyUtil.copy(req,Content.class); 98 | if (ObjectUtils.isEmpty(req.getId())){ 99 | //新增 100 | doc.setId(snowFlake.nextId()); 101 | doc.setViewCount(0); 102 | doc.setVoteCount(0); 103 | docMapper.insert(doc); 104 | 105 | content.setId(doc.getId()); 106 | contentMapper.insert(content); 107 | }else { 108 | //更新 109 | docMapper.updateByPrimaryKey(doc); 110 | int count = contentMapper.updateByPrimaryKeyWithBLOBs(content); 111 | if (count == 0){ 112 | contentMapper.insert(content); 113 | } 114 | } 115 | } 116 | 117 | /** 118 | * 删除 119 | */ 120 | public void delete(Long id){ 121 | docMapper.deleteByPrimaryKey(id); 122 | } 123 | 124 | public void delete(List ids){ 125 | DocExample docExample = new DocExample(); 126 | DocExample.Criteria criteria = docExample.createCriteria(); 127 | List list = new ArrayList<>(); 128 | for (int i = 0; i < ids.size(); i++){ 129 | list.add(i,Long.parseLong(ids.get(i))); 130 | } 131 | criteria.andIdIn(list); 132 | docMapper.deleteByExample(docExample); 133 | } 134 | 135 | public String findContent(Long id){ 136 | Content content = contentMapper.selectByPrimaryKey(id); 137 | // 文档阅读数 + 1 138 | docMapperCust.increaseViewCount(id); 139 | if (ObjectUtils.isEmpty(content)){ 140 | return ""; 141 | }else { 142 | return content.getContent(); 143 | } 144 | } 145 | 146 | // 点赞 147 | public void vote(Long id) { 148 | // docMapperCust.increaseVoteCount(id); 149 | 150 | //远程IP+doc.id作为key, 24小时内不能重复 151 | String ip = RequestContext.getRemoteAddr(); 152 | if (redisUtil.validateRepeat("DOC_VOTE_" + id + "_" + ip, 3600 * 24)) { 153 | docMapperCust.increaseVoteCount(id); 154 | } else { 155 | throw new BusinessException(BusinessExceptionCode.VOTE_REPEAT); 156 | } 157 | 158 | // 推送消息 159 | Doc docDb = docMapper.selectByPrimaryKey(id); 160 | String logId = MDC.get("LOG_ID"); 161 | wsService.sendInfo("【" + docDb.getName() + "】此刻被赞了一下", logId); 162 | } 163 | 164 | 165 | 166 | 167 | public void updateEbookInfo() { 168 | docMapperCust.updateEbookInfo(); 169 | } 170 | 171 | } 172 | -------------------------------------------------------------------------------- /src/main/resources/mapper/DemoMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | and ${criterion.condition} 17 | 18 | 19 | and ${criterion.condition} #{criterion.value} 20 | 21 | 22 | and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} 23 | 24 | 25 | and ${criterion.condition} 26 | 27 | #{listItem} 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | and ${criterion.condition} 46 | 47 | 48 | and ${criterion.condition} #{criterion.value} 49 | 50 | 51 | and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} 52 | 53 | 54 | and ${criterion.condition} 55 | 56 | #{listItem} 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | id, `name` 68 | 69 | 83 | 89 | 90 | delete from demo 91 | where id = #{id,jdbcType=BIGINT} 92 | 93 | 94 | delete from demo 95 | 96 | 97 | 98 | 99 | 100 | insert into demo (id, `name`) 101 | values (#{id,jdbcType=BIGINT}, #{name,jdbcType=VARCHAR}) 102 | 103 | 104 | insert into demo 105 | 106 | 107 | id, 108 | 109 | 110 | `name`, 111 | 112 | 113 | 114 | 115 | #{id,jdbcType=BIGINT}, 116 | 117 | 118 | #{name,jdbcType=VARCHAR}, 119 | 120 | 121 | 122 | 128 | 129 | update demo 130 | 131 | 132 | id = #{record.id,jdbcType=BIGINT}, 133 | 134 | 135 | `name` = #{record.name,jdbcType=VARCHAR}, 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | update demo 144 | set id = #{record.id,jdbcType=BIGINT}, 145 | `name` = #{record.name,jdbcType=VARCHAR} 146 | 147 | 148 | 149 | 150 | 151 | update demo 152 | 153 | 154 | `name` = #{name,jdbcType=VARCHAR}, 155 | 156 | 157 | where id = #{id,jdbcType=BIGINT} 158 | 159 | 160 | update demo 161 | set `name` = #{name,jdbcType=VARCHAR} 162 | where id = #{id,jdbcType=BIGINT} 163 | 164 | -------------------------------------------------------------------------------- /web/src/views/admin/admin-category.vue: -------------------------------------------------------------------------------- 1 | 96 | 97 | 248 | 249 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 124 | 125 | FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 162 | if ERRORLEVEL 1 goto error 163 | goto end 164 | 165 | :error 166 | set ERROR_CODE=1 167 | 168 | :end 169 | @endlocal & set ERROR_CODE=%ERROR_CODE% 170 | 171 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 172 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 173 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 174 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 175 | :skipRcPost 176 | 177 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 178 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 179 | 180 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 181 | 182 | exit /B %ERROR_CODE% 183 | -------------------------------------------------------------------------------- /web/src/views/doc.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 151 | 152 | 225 | -------------------------------------------------------------------------------- /src/main/resources/mapper/ContentMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | and ${criterion.condition} 19 | 20 | 21 | and ${criterion.condition} #{criterion.value} 22 | 23 | 24 | and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} 25 | 26 | 27 | and ${criterion.condition} 28 | 29 | #{listItem} 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | and ${criterion.condition} 48 | 49 | 50 | and ${criterion.condition} #{criterion.value} 51 | 52 | 53 | and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} 54 | 55 | 56 | and ${criterion.condition} 57 | 58 | #{listItem} 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | id 70 | 71 | 72 | content 73 | 74 | 90 | 104 | 112 | 113 | delete from content 114 | where id = #{id,jdbcType=BIGINT} 115 | 116 | 117 | delete from content 118 | 119 | 120 | 121 | 122 | 123 | insert into content (id, content) 124 | values (#{id,jdbcType=BIGINT}, #{content,jdbcType=LONGVARCHAR}) 125 | 126 | 127 | insert into content 128 | 129 | 130 | id, 131 | 132 | 133 | content, 134 | 135 | 136 | 137 | 138 | #{id,jdbcType=BIGINT}, 139 | 140 | 141 | #{content,jdbcType=LONGVARCHAR}, 142 | 143 | 144 | 145 | 151 | 152 | update content 153 | 154 | 155 | id = #{record.id,jdbcType=BIGINT}, 156 | 157 | 158 | content = #{record.content,jdbcType=LONGVARCHAR}, 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | update content 167 | set id = #{record.id,jdbcType=BIGINT}, 168 | content = #{record.content,jdbcType=LONGVARCHAR} 169 | 170 | 171 | 172 | 173 | 174 | update content 175 | set id = #{record.id,jdbcType=BIGINT} 176 | 177 | 178 | 179 | 180 | 181 | update content 182 | 183 | 184 | content = #{content,jdbcType=LONGVARCHAR}, 185 | 186 | 187 | where id = #{id,jdbcType=BIGINT} 188 | 189 | 190 | update content 191 | set content = #{content,jdbcType=LONGVARCHAR} 192 | where id = #{id,jdbcType=BIGINT} 193 | 194 | -------------------------------------------------------------------------------- /src/main/resources/mapper/CategoryMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | and ${criterion.condition} 19 | 20 | 21 | and ${criterion.condition} #{criterion.value} 22 | 23 | 24 | and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} 25 | 26 | 27 | and ${criterion.condition} 28 | 29 | #{listItem} 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | and ${criterion.condition} 48 | 49 | 50 | and ${criterion.condition} #{criterion.value} 51 | 52 | 53 | and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} 54 | 55 | 56 | and ${criterion.condition} 57 | 58 | #{listItem} 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | id, parent, `name`, sort 70 | 71 | 85 | 91 | 92 | delete from category 93 | where id = #{id,jdbcType=BIGINT} 94 | 95 | 96 | delete from category 97 | 98 | 99 | 100 | 101 | 102 | insert into category (id, parent, `name`, 103 | sort) 104 | values (#{id,jdbcType=BIGINT}, #{parent,jdbcType=BIGINT}, #{name,jdbcType=VARCHAR}, 105 | #{sort,jdbcType=INTEGER}) 106 | 107 | 108 | insert into category 109 | 110 | 111 | id, 112 | 113 | 114 | parent, 115 | 116 | 117 | `name`, 118 | 119 | 120 | sort, 121 | 122 | 123 | 124 | 125 | #{id,jdbcType=BIGINT}, 126 | 127 | 128 | #{parent,jdbcType=BIGINT}, 129 | 130 | 131 | #{name,jdbcType=VARCHAR}, 132 | 133 | 134 | #{sort,jdbcType=INTEGER}, 135 | 136 | 137 | 138 | 144 | 145 | update category 146 | 147 | 148 | id = #{record.id,jdbcType=BIGINT}, 149 | 150 | 151 | parent = #{record.parent,jdbcType=BIGINT}, 152 | 153 | 154 | `name` = #{record.name,jdbcType=VARCHAR}, 155 | 156 | 157 | sort = #{record.sort,jdbcType=INTEGER}, 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | update category 166 | set id = #{record.id,jdbcType=BIGINT}, 167 | parent = #{record.parent,jdbcType=BIGINT}, 168 | `name` = #{record.name,jdbcType=VARCHAR}, 169 | sort = #{record.sort,jdbcType=INTEGER} 170 | 171 | 172 | 173 | 174 | 175 | update category 176 | 177 | 178 | parent = #{parent,jdbcType=BIGINT}, 179 | 180 | 181 | `name` = #{name,jdbcType=VARCHAR}, 182 | 183 | 184 | sort = #{sort,jdbcType=INTEGER}, 185 | 186 | 187 | where id = #{id,jdbcType=BIGINT} 188 | 189 | 190 | update category 191 | set parent = #{parent,jdbcType=BIGINT}, 192 | `name` = #{name,jdbcType=VARCHAR}, 193 | sort = #{sort,jdbcType=INTEGER} 194 | where id = #{id,jdbcType=BIGINT} 195 | 196 | --------------------------------------------------------------------------------