Browse Source

生成词云,用户画像

zhangshuling 1 year ago
parent
commit
91dca5cddf

+ 133 - 0
xzl-admin/src/main/java/com/xzl/web/controller/userPortrait/ViewController.java

@@ -0,0 +1,133 @@
+package com.xzl.web.controller.userPortrait;
+
+import com.xzl.common.utils.DateUtils;
+import org.aspectj.util.FileUtil;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponse;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @ClassName: ViewController
+ * @Description: 用于
+ * Modification History:
+ * Date                  Author                 Version       Description
+ * ---------------------------------------------------------
+ * 2023/11/18             ZhangShuling      v1.0.0
+ */
+@RestController
+@RequestMapping("/user-portait")
+public class ViewController {
+
+  String path = "/usr/local/python/";
+
+
+  @GetMapping("/show")
+  public void show(HttpServletResponse response) throws Exception {
+    String wordcloudImageFileName = "wordcloud.png";
+    File wordcloudImageFile = new File(path, wordcloudImageFileName);
+    if (wordcloudImageFile.exists()) {
+      InputStream inputStream = null;
+      ServletOutputStream outputStream = null;
+      try {
+        inputStream = new FileInputStream(wordcloudImageFile);
+        outputStream = response.getOutputStream();
+        response.setHeader("Content-Disposition", String.format(
+          "attachment; filename=\"%s\"",
+          java.net.URLEncoder.encode(wordcloudImageFile.getName(), "UTF-8")));
+        response.setContentType("application/octet-stream;charset=UTF-8");
+        int count = 0;
+        byte[] buffer = new byte[1024 * 1024];
+        while ((count = inputStream.read(buffer)) != -1) {
+          outputStream.write(buffer, 0, count);
+        }
+        outputStream.flush();
+      } catch (Exception e) {
+        e.printStackTrace();
+      } finally {
+        if (inputStream != null) {
+          inputStream.close();
+        }
+        if (outputStream != null) {
+          outputStream.close();
+        }
+      }
+    }
+  }
+
+  @PostMapping("/generate")
+  public Map generate() throws Exception {
+    Map rs = new HashMap();
+    // 先判断有没有 wordcloud.txt, 没有就生成一个,有就备份一个,然后重新生成新的内容
+    String wordcloudFileName = "wordcloud.txt";
+    File wordcloudFile = new File(path, wordcloudFileName);
+
+    String wordcloudImageFileName = "wordcloud.png";
+    File wordcloudImageFile = new File(path, wordcloudImageFileName);
+
+    // 如果词云文件存在,就先都备份一下,然后再生成新的
+    String timePrefix = DateUtils.dateTimeNow();
+    if (wordcloudFile.exists()) {
+      FileUtil.copyFile(wordcloudFile, new File(path, timePrefix + "-" + wordcloudFileName));
+    }
+    if (wordcloudImageFile.exists()) {
+      FileUtil.copyFile(wordcloudImageFile, new File(path, timePrefix + "-" + wordcloudImageFileName));
+    }
+    // 获取新的内容,写入 wordcloud.txt
+
+    // 调用python脚本, 生成新的词云图片
+    String[] commands = {"/usr/local/Cellar/python@3.10/3.10.11/bin/python3.10", "/usr/local/python/word-cloud.py"};
+    ProcessBuilder processBuilder = new ProcessBuilder(commands);
+    Process process = processBuilder.start();
+
+    // 读取命令执行结果
+//    BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+//    String line;
+//    while ((line = reader.readLine()) != null) {
+//      System.out.println(line);
+//    }
+    // 等待命令执行完成
+    int exitCode = process.waitFor();
+    System.out.println("命令执行结果:" + exitCode);
+    rs.put("exitCode", exitCode);
+    return rs;
+  }
+
+
+  /**
+   * 执行Linux命令
+   *
+   * @param command 需要执行的Linux命令
+   * @return 执行结果
+   */
+  public String executeCommand(String command) throws IOException {
+    // 创建一个新进程
+    Process process = Runtime.getRuntime().exec(command);
+
+    // 获取进程的输入流并转换为字符串
+    InputStream inputStream = process.getInputStream();
+    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
+    StringBuilder result = new StringBuilder();
+    String line;
+    while ((line = reader.readLine()) != null) {
+      result.append(line).append("\n");
+    }
+
+    // 关闭输入流和进程
+    reader.close();
+    process.destroy();
+
+    return result.toString();
+  }
+}

+ 1 - 1
xzl-admin/src/main/resources/application.yml

@@ -133,4 +133,4 @@ xss:
 # binglog需要监控的表
 mysql:
   binlog:
-    tables: db_cgb.sys_log, db_cgb.3rd_file ,xtdb_standard.buss_file,xtdb.sys_logininfor
+    tables: xtdb.interface_cm_customer_extend_info, xtdb.interface_md_customer_delivery ,xtdb.interface_md_customer_info,xtdb.interface_md_customer_marketing,xtdb.interface_md_employee,xtdb.interface_md_org_unit

BIN
xzl-admin/src/main/resources/python/HanZiZhiMeiFangSongGBK-MianFei.ttf


+ 2 - 0
xzl-admin/src/main/resources/python/README

@@ -0,0 +1,2 @@
+pip install WordCloud
+pip install jieba

BIN
xzl-admin/src/main/resources/python/people.png


+ 28 - 0
xzl-admin/src/main/resources/python/word-cloud.py

@@ -0,0 +1,28 @@
+import jieba
+import numpy as np
+from PIL import Image
+from wordcloud import WordCloud
+
+def trans_CN(text):
+    word_list = jieba.cut(text)
+    result = " ".join(word_list)
+    return result
+
+
+#local = './'
+local = '/usr/local/python/'
+
+# text = "大家好我是空空star我爱发动态我喜欢使用搜索引擎模式进行分词"
+with open(local + "wordcloud.txt") as fp:
+    text = fp.read()
+    cut_text = trans_CN(text)
+# cut_text = " ".join(jieba.cut_for_search(text))
+# mask = np.array(Image.open(local + "baozi.png"))
+mask = np.array(Image.open(local + "people.png"))
+
+wc = WordCloud(font_path=local + "/HanZiZhiMeiFangSongGBK-MianFei.ttf",  # 设置字体
+               background_color='white',  # 设置背景颜色
+               mask=mask  # 设置背景图片
+               )
+wc.generate(cut_text)
+wc.to_file(local + "wordcloud.png")

+ 11 - 0
xzl-admin/src/main/resources/python/wordcloud.txt

@@ -0,0 +1,11 @@
+好猫(金丝猴)
+黄鹤楼(软蓝)
+双喜(软经典)
+黄山(小红方印)
+槟榔口味王
+黄鹤楼(软红)
+炫迈口想糖
+七匹狼(红)
+黄鹤楼(软雪之景)
+黄金叶(浓香中支)
+黄鹤楼(硬雅韵)

+ 109 - 112
xzl-framework/src/main/java/com/xzl/framework/config/SecurityConfig.java

@@ -22,127 +22,124 @@ import com.xzl.framework.security.handle.LogoutSuccessHandlerImpl;
 
 /**
  * spring security配置
- * 
+ *
  * @author xzl
  */
 @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
-public class SecurityConfig extends WebSecurityConfigurerAdapter
-{
-    /**
-     * 自定义用户认证逻辑
-     */
-    @Autowired
-    private UserDetailsService userDetailsService;
-    
-    /**
-     * 认证失败处理类
-     */
-    @Autowired
-    private AuthenticationEntryPointImpl unauthorizedHandler;
+public class SecurityConfig extends WebSecurityConfigurerAdapter {
+  /**
+   * 自定义用户认证逻辑
+   */
+  @Autowired
+  private UserDetailsService userDetailsService;
 
-    /**
-     * 退出处理类
-     */
-    @Autowired
-    private LogoutSuccessHandlerImpl logoutSuccessHandler;
+  /**
+   * 认证失败处理类
+   */
+  @Autowired
+  private AuthenticationEntryPointImpl unauthorizedHandler;
 
-    /**
-     * token认证过滤器
-     */
-    @Autowired
-    private JwtAuthenticationTokenFilter authenticationTokenFilter;
-    
-    /**
-     * 跨域过滤器
-     */
-    @Autowired
-    private CorsFilter corsFilter;
+  /**
+   * 退出处理类
+   */
+  @Autowired
+  private LogoutSuccessHandlerImpl logoutSuccessHandler;
 
-    /**
-     * 允许匿名访问的地址
-     */
-    @Autowired
-    private PermitAllUrlProperties permitAllUrl;
+  /**
+   * token认证过滤器
+   */
+  @Autowired
+  private JwtAuthenticationTokenFilter authenticationTokenFilter;
 
-    /**
-     * 解决 无法直接注入 AuthenticationManager
-     *
-     * @return
-     * @throws Exception
-     */
-    @Bean
-    @Override
-    public AuthenticationManager authenticationManagerBean() throws Exception
-    {
-        return super.authenticationManagerBean();
-    }
+  /**
+   * 跨域过滤器
+   */
+  @Autowired
+  private CorsFilter corsFilter;
 
-    /**
-     * anyRequest          |   匹配所有请求路径
-     * access              |   SpringEl表达式结果为true时可以访问
-     * anonymous           |   匿名可以访问
-     * denyAll             |   用户不能访问
-     * fullyAuthenticated  |   用户完全认证可以访问(非remember-me下自动登录)
-     * hasAnyAuthority     |   如果有参数,参数表示权限,则其中任何一个权限可以访问
-     * hasAnyRole          |   如果有参数,参数表示角色,则其中任何一个角色可以访问
-     * hasAuthority        |   如果有参数,参数表示权限,则其权限可以访问
-     * hasIpAddress        |   如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
-     * hasRole             |   如果有参数,参数表示角色,则其角色可以访问
-     * permitAll           |   用户可以任意访问
-     * rememberMe          |   允许通过remember-me登录的用户访问
-     * authenticated       |   用户登录后可访问
-     */
-    @Override
-    protected void configure(HttpSecurity httpSecurity) throws Exception
-    {
-        // 注解标记允许匿名访问的url
-        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();
-        permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll());
+  /**
+   * 允许匿名访问的地址
+   */
+  @Autowired
+  private PermitAllUrlProperties permitAllUrl;
 
-        httpSecurity
-                // CSRF禁用,因为不使用session
-                .csrf().disable()
-                // 禁用HTTP响应标头
-                .headers().cacheControl().disable().and()
-                // 认证失败处理类
-                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
-                // 基于token,所以不需要session
-                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
-                // 过滤请求
-                .authorizeRequests()
-                // 对于登录login 注册register 验证码captchaImage 允许匿名访问
-                .antMatchers("/login", "/register", "/captchaImage").permitAll()
-                // 静态资源,可匿名访问
-                .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
-                .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
-                // 除上面外的所有请求全部需要鉴权认证
-                .anyRequest().authenticated()
-                .and()
-                .headers().frameOptions().disable();
-        // 添加Logout filter
-        httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
-        // 添加JWT filter
-        httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
-        // 添加CORS filter
-        httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
-        httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);
-    }
+  /**
+   * 解决 无法直接注入 AuthenticationManager
+   *
+   * @return
+   * @throws Exception
+   */
+  @Bean
+  @Override
+  public AuthenticationManager authenticationManagerBean() throws Exception {
+    return super.authenticationManagerBean();
+  }
 
-    /**
-     * 强散列哈希加密实现
-     */
-    @Bean
-    public BCryptPasswordEncoder bCryptPasswordEncoder()
-    {
-        return new BCryptPasswordEncoder();
-    }
+  /**
+   * anyRequest          |   匹配所有请求路径
+   * access              |   SpringEl表达式结果为true时可以访问
+   * anonymous           |   匿名可以访问
+   * denyAll             |   用户不能访问
+   * fullyAuthenticated  |   用户完全认证可以访问(非remember-me下自动登录)
+   * hasAnyAuthority     |   如果有参数,参数表示权限,则其中任何一个权限可以访问
+   * hasAnyRole          |   如果有参数,参数表示角色,则其中任何一个角色可以访问
+   * hasAuthority        |   如果有参数,参数表示权限,则其权限可以访问
+   * hasIpAddress        |   如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
+   * hasRole             |   如果有参数,参数表示角色,则其角色可以访问
+   * permitAll           |   用户可以任意访问
+   * rememberMe          |   允许通过remember-me登录的用户访问
+   * authenticated       |   用户登录后可访问
+   */
+  @Override
+  protected void configure(HttpSecurity httpSecurity) throws Exception {
+    // 注解标记允许匿名访问的url
+    ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();
+    permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll());
 
-    /**
-     * 身份认证接口
-     */
-    @Override
-    protected void configure(AuthenticationManagerBuilder auth) throws Exception
-    {
-        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
-    }
+    httpSecurity
+      // CSRF禁用,因为不使用session
+      .csrf().disable()
+      // 禁用HTTP响应标头
+      .headers().cacheControl().disable().and()
+      // 认证失败处理类
+      .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
+      // 基于token,所以不需要session
+      .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
+      // 过滤请求
+      .authorizeRequests()
+      // 对于登录login 注册register 验证码captchaImage 允许匿名访问
+      .antMatchers("/login", "/register", "/captchaImage").permitAll()
+      // 用户画像
+      .antMatchers("/user-portait/**").permitAll()
+      // 静态资源,可匿名访问
+      .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
+      .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
+      // 除上面外的所有请求全部需要鉴权认证
+      .anyRequest().authenticated()
+      .and()
+      .headers().frameOptions().disable();
+    // 添加Logout filter
+    httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
+    // 添加JWT filter
+    httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
+    // 添加CORS filter
+    httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
+    httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);
+  }
+
+  /**
+   * 强散列哈希加密实现
+   */
+  @Bean
+  public BCryptPasswordEncoder bCryptPasswordEncoder() {
+    return new BCryptPasswordEncoder();
+  }
+
+  /**
+   * 身份认证接口
+   */
+  @Override
+  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+    auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
+  }
 }

+ 11 - 0
xzl-ui/src/api/tool/gen.js

@@ -8,6 +8,7 @@ export function listTable(query) {
     params: query
   })
 }
+
 // 查询db数据库列表
 export function listDbTable(query) {
   return request({
@@ -74,3 +75,13 @@ export function synchDb(tableName) {
     method: 'get'
   })
 }
+
+
+// 生成用户画像
+export function generateUserPortait() {
+  return request({
+    url: '/user-portait/generate',
+    method: 'post'
+  })
+}
+

+ 47 - 0
xzl-ui/src/views/user/portrait.vue

@@ -0,0 +1,47 @@
+<template>
+  <div>
+    <img :src="imageUrl" class="user-portrait"/>
+    <div class="but-box">
+      <el-button @click="generate()">重新生成</el-button>
+    </div>
+  </div>
+</template>
+<script>
+  import {generateUserPortait} from '@/api/tool/gen'
+
+  export default {
+    name: "portrait",
+    data() {
+      return {
+        imageUrl: "",
+      }
+    },
+    created() {
+      this.imageUrl = "../dev-api/user-portait/show"
+    },
+    methods: {
+      generate() {
+        this.$modal.loading("正在生成用户画像,请稍候...");
+        generateUserPortait().then(rs => {
+          console.log('rs -====>', rs)
+          this.imageUrl = "../dev-api/user-portait/show?" + new Date().getTime()
+          this.$modal.closeLoading()
+        })
+      }
+    }
+  }
+</script>
+
+<style scoped>
+  .user-portrait {
+    width: 50%;
+  }
+
+  .but-box {
+    width: 300px;
+    height: 60px;
+    position: absolute;
+    top: 10px;
+    right: 0px;
+  }
+</style>